# USDA Local Food Directory: Farmers Markets, CSAs, Food Hubs (`flipvo/usda-local-food-directory`) Actor

Every listing in the USDA Local Food Portal (farmers markets, CSAs, food hubs, on-farm markets, agritourism) in one schema. $2.00 per 1,000 listings. Fields: name, street, city, state, zip (Census Geocoder fills USDA's gaps; see zipSource), latitude, longitude, updatedOn, listingUrl.

- **URL**: https://apify.com/flipvo/usda-local-food-directory.md
- **Developed by:** [Christopher Nowlan](https://apify.com/flipvo) (community)
- **Categories:** Lead generation, Business
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$2.00 / 1,000 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

## USDA Local Food Directory: Farmers Markets, CSAs, Food Hubs

USDA's bulk export is free; you pay for the work already done on it. **One schema across five directories** (farmers
markets, CSAs, food hubs, on-farm markets, agritourism: about 28,000 listings), addresses parsed into street, city,
state and ZIP with the ZIPs USDA left out filled from the US Census Geocoder (98.6% of Texas farmers markets and CSAs
have one), numeric coordinates, real lists, duplicates removed, state, keyword and last-update filters, a schedule to
keep it fresh, and you pay only for the rows you keep: **$2.00 per 1,000 listings**, about $56 for the whole country.

Every row also has the last-update date, the products, production methods (organic, no pesticides, ...), accepted
payments and food-assistance programs (SNAP, WIC) the listing reports, and a link to the listing's page on the USDA
portal. Use it for a local-food map, a CRM list or a research dataset.

No login, no proxy, no browser. The Actor reads the portal's public bulk-export files (one request per directory) and,
for addresses USDA publishes without a ZIP, asks the free US Census Geocoder for it (see `fillMissingZip`), so a full
run of all five directories takes about two to three minutes and a default run about half a minute.

### What you get

- **Five directories in one schema**: `farmersmarket`, `csa`, `foodhub`, `onfarmmarket`, `agritourism`.
- **Parsed addresses**: USDA publishes one free-text address per listing in many shapes. The Actor reads `street`,
  `city`, `state` (two-letter code) and `zip` from it and always keeps the original text in `address`. Anything it
  cannot read with confidence is `null`, never guessed.
- **ZIP codes USDA left out, filled in**: about 6% of addresses (11% in Texas) have a street, city and state but no
  ZIP (`10401 Scott Street, Houston, TX, USA`). The Actor looks those up in the official, free
  [US Census Geocoder](https://geocoding.geo.census.gov/geocoder/) by street, city and state (never by coordinates) and
  sets `zip` with `zipSource: "census-geocoder"`. A ZIP USDA published stays `zipSource: "usda"`; no ZIP is `null`.
- **Clean values**: coordinates as numbers (blank and placeholder values become `null`), update dates as
  `YYYY-MM-DD`, the portal's `;`-joined answers as real lists, and long text with line breaks tidied.
- **Filters**: states, keywords (name, description, city, products, production methods) and "updated on or after".
- Polite by design: robots.txt is read on every run, requests are spaced, failures are retried with backoff.

### Use from AI agents (Apify MCP)

Add the Actor to an MCP client with `https://mcp.apify.com/?actors=flipvo/usda-local-food-directory`. It appears as one
tool, `flipvo--usda-local-food-directory`, whose arguments are the fields below. Nothing is required: a call with no
arguments reads the farmers-market directory. Pass `directories` and `states` (lists) to narrow it. The result names
the run's dataset; fetch the rows with the dataset tool and ask for the fields you need, for example
`name,city,state,zip,latitude,longitude,updatedOn,listingUrl`.

### Input

| Field            | What it does                                                                                            | Default         |
| ---------------- | ------------------------------------------------------------------------------------------------------- | --------------- |
| `directories`    | Which directories to read: `farmersmarket`, `csa`, `foodhub`, `onfarmmarket`, `agritourism`             | `farmersmarket` |
| `states`         | Keep only these states, as codes (`TX`) or names (`Texas`)                                              | whole country   |
| `keywords`       | Keep listings whose name, description, city, products or production methods contain any of these words  | all listings    |
| `updatedAfter`   | Keep listings USDA updated on or after this date (`YYYY-MM-DD`); undated listings are left out when set | all listings    |
| `maxItems`       | Stop after this many delivered listings (your budget cap)                                               | 1000            |
| `requestDelayMs` | Minimum gap between requests to the portal (500 to 10000)                                               | 1000            |
| `maxRetries`     | Retries with backoff for 429, 5xx and network errors (0 to 6)                                           | 3               |
| `fillMissingZip` | Look up the ZIP of addresses USDA published without one in the US Census Geocoder (free)                | `true`          |

Example: organic-minded Texas and Oklahoma CSAs and farmers markets updated since 2024:

```json
{
    "directories": ["farmersmarket", "csa"],
    "states": ["TX", "OK"],
    "keywords": ["organic"],
    "updatedAfter": "2024-01-01",
    "maxItems": 5000
}
```

### Output

One dataset row per listing. [`docs/sample-output.json`](docs/sample-output.json) holds one real row from each
directory from a real run, plus one whose ZIP came from the Census Geocoder; a row looks like this:

```json
{
    "directory": "csa",
    "listingId": "202117",
    "name": "Tasty Little Leaves Microgreens, LLC",
    "address": "Jacksonville, FL 32246, USA",
    "street": null,
    "city": "Jacksonville",
    "state": "FL",
    "zip": "32246",
    "zipSource": "usda",
    "latitude": 30.309322,
    "longitude": -81.504777,
    "updatedOn": "2026-07-23",
    "products": ["Microgreens"],
    "seasonMonths": "Year-round",
    "productionMethods": ["Non-Certified, but Practicing Organic", "Non-GMO", "No pesticides"],
    "listingUrl": "https://www.usdalocalfoodportal.com/fe/flisting/?directory_type=csa&lid=202117",
    "scrapedAt": "2026-11-04T20:05:25.869Z"
}
```

Each directory has the columns USDA gives it, and a field a directory has no column for is left out of its rows
rather than delivered empty: `products` and `seasonMonths` for CSAs and food hubs, `shareSizes` for CSAs, `facilities`
for on-farm markets and agritourism, `services` for food hubs and agritourism, `venue` for farmers markets,
`legalStatus` and `yearEstablished` for food hubs, `deliveryMethods` for every directory but food hubs. The farmers
market export has no product list, season or opening hours, so farmers market rows carry none. A field the directory
has but the listing left blank is `null` or `[]`. `zipSource` says where `zip` comes from: `usda` (written in the
address), `census-geocoder` (matched in the US Census Geocoder, see below) or `null` (no ZIP). A listing's `zip` is
`null` only where USDA lists none and the Census Geocoder cannot match the street in that city (for example a highway
without a house number); it is never guessed and never derived from coordinates.
Export as JSON, CSV, Excel or via the API. A per-directory summary (rows in the export, `skippedUnreadable`, `skippedDuplicate`, matched, delivered, error) is
saved to the `SUMMARY` record of the run's key-value store.

A real run of all five directories on 2026-11-04 delivered 27,956 listings: 7,184 farmers markets, 2,008 CSAs, 483 food
hubs, 4,707 on-farm markets and 13,574 agritourism farms. Measured on that data, the state was readable for 99% of
listings, the city for 97%, the ZIP for 96% (92% from USDA's addresses alone, the rest from the Census Geocoder),
coordinates for 99% and a last-update date for 86%. In Texas farmers markets and CSAs (285 listings) the city was read
for every listing and the ZIP for 98.6% (253 from USDA's addresses, 28 from the Census Geocoder; 88.8% before the
geocoder).

#### Address accuracy, measured

Filled is not the same as right, so the parsed fields were checked too. On 2026-11-05 (05:03Z to 05:04Z UTC), 40
listings were drawn at random (seed 42) from each of the five directories of that day's export (27,956 listings). Each
`street`, `city`, `state` and `zip` was checked by hand against USDA's own `address` text. Every ZIP filled from the
Census Geocoder was checked against a second, independent Census answer: the ZIP Code Tabulation Area at USDA's own
coordinates.

| Field                          | Right                                                  | Wrong value | Left null though the text states it | Wrong, population-weighted |
| ------------------------------ | ------------------------------------------------------ | ----------- | ----------------------------------- | -------------------------- |
| `street`                       | 200 of 200                                             | 0           | 0                                   | 0.0%                       |
| `city`                         | 198 of 200                                             | 0           | 2                                   | 0.0%                       |
| `state`                        | 199 of 200                                             | 0           | 1                                   | 0.0%                       |
| `zip`                          | 199 of 200                                             | 0           | 1                                   | 0.0%                       |
| `zip` from the Census Geocoder | 12 of 12 agree with the ZIP area at USDA's coordinates |             |                                     |                            |
| Before this release's fix      | `city` wrong 2, `street` wrong 2                       |             |                                     |                            |

- **Zero wrong values in 200** puts the true wrong-value rate below about 1.5% (95% confidence, the rule of three).
- **The fix this check found:** a unit or a landmark written before the city with no comma ("2nd Floor Great
  Barrington", "behind Ritz Theater Newburgh") became part of the city. It is now cut off into `street`, but only when
  the words before the city are plainly not a place name (a number, "Suite", "lot", "behind" and the like) and the
  rest is a Census place. Across the whole export this changed 54 listings, and every one of them got the right city.
- **Left null:** a lone "Chester" with no state, and "01072, Shutesbury, Massachu" (the ZIP first and the state cut
  short). The full `address` is still delivered.
- **Checked against the text as written:** "Idaho Falls, IA" is delivered as `IA` (Iowa), because that is what USDA
  wrote.

The sample, with each listing's link, USDA's text, our answer and the hand-checked answer, is kept with the Actor's source
(`docs/address-accuracy-2026-11.json`), next to the scripts that draw, re-parse and score it. A test fails the build if
this table stops matching that file. Ask in the Issues tab for a copy.

### Pricing

Pay per event: **$2.00 per 1,000 listings delivered** ($0.002 per listing). Listings you filter out, duplicate rows and
unreadable rows are never charged. Apify also charges its standard start event, $0.00005 per run. Platform usage is not
charged on top. `maxItems` caps the bill: $2.00 at the default of 1,000. All five directories for Texas came to 892
listings on 2026-11-04, about $1.78; the whole country (27,956 listings) is about $55.91 for a full refresh.

The five directories in one schema, the address work, the cleaned values, the real lists, the removed duplicates and
the filters are described above; if you only need one raw file once, download it from USDA at usdalocalfoodportal.com.

### Good to know

- **No phone numbers, emails or contact names.** The public bulk export does not contain them, so this Actor does not
  return them. It does not open listing pages or look anywhere else for them.
- The data is what listings reported to USDA, and many listings are old: the `updatedOn` date tells you how old.
  Use `updatedAfter` to keep only recently confirmed ones. Listings with no date in the export have `updatedOn: null`.
- The export has no separate state, city or ZIP columns. They are read from the address text (state names and codes,
  ZIP and ZIP+4, a missing comma before the city, ZIPs that lost their leading zero), so about 1% of listings have no
  `state` and about 3% no `city` (the full `address` is still there). `address` is always USDA's text; a ZIP from the
  Census Geocoder is added to `zip` only, not to `address`. When you filter by `states`, listings with no
  readable state are left out.
- A listing id that appears twice in one directory's export is delivered once and counted in `skippedDuplicate`;
  rows without an id or name are counted in `skippedUnreadable`. Neither is charged.
- The same farm can appear in more than one directory (for example a CSA that also runs a farm stand). Listing ids are
  unique within a directory, so use `directory` + `listingId` as the key.
- The portal sometimes answers with an error page instead of data. The Actor treats that as a failed directory,
  reports it in `SUMMARY` and still delivers the other directories; if every directory fails the run fails.
- A run keeps the whole export of one directory in memory (the largest is about 40 MB); 512 MB of run memory is enough.

### ZIP codes from the Census Geocoder

With `fillMissingZip` on (the default), every delivered row that has a street, a city and a state but no ZIP is sent
to the [US Census Geocoder](https://geocoding.geo.census.gov/geocoder/) (`locations/addressbatch`, benchmark
`Public_AR_Current`, up to 250 addresses per request; the geocoder's documented limit is 10,000). It is a free public
service of the US Census Bureau with no robots.txt (so nothing is disallowed), and the Actor sends the same self-identifying
User-Agent as for the portal. A ZIP is taken from a match only when:

- the match type is `Exact` or `Non_Exact` (a tie or no match gives nothing), and
- the matched **state and city are the ones in USDA's address** (spelling variants such as `Lee's Summit` and
  `LEES SUMMIT`, `Mt` and `Mount`, `Jackson Township` and `Jackson` count as the same).

The city check matters for `Non_Exact` matches, where the geocoder adjusts the street: "Between 4th and 5th Avenue,
Garnett, KS" came back as an intersection in Lenora, KS, and is refused. Only the ZIP in the matched address text is
used; the coordinates the geocoder returns are ignored.

How right is it? On 2026-11-04 the ZIP of 3,000 addresses that USDA does publish with a ZIP was withheld and looked up
the same way: of the 2,136 that passed the rule above, 96% came back with the same ZIP USDA published (97% of the
`Exact` ones, 93% of the `Non_Exact` ones), and 99% with the same first three digits; the others are usually the
neighbouring ZIP of the same town, because the geocoder reads the ZIP from Census address ranges, not from the postal
service's delivery file. A spot check of 20 geocoded Texas addresses against a second Census call (the one-line address endpoint) and against the
ZIP area at USDA's own coordinates found the same ZIP for 20 of 20 and 19 of 19 readable (the area lookup returned
nothing for one). The geocoder is best effort: it is
sometimes slow, so a run may wait up to about 75 seconds for one batch; if a batch cannot be answered the run logs a
warning, those rows keep `zip: null`, after two failed batches in a row it stops asking, and the run never fails
because of it. Turn `fillMissingZip` off to get USDA's addresses exactly as published.

### Legal and data note

This Actor reads the **USDA Local Food Portal's public bulk export**, federal data that USDA Agricultural Marketing
Service publishes for anyone to download, through the portal's public download endpoints. It does not log in, bypass
any protection or use proxies, and it checks each host's robots.txt before every run (neither the portal nor the Census Geocoder publishes one, so
nothing is disallowed). The portal rejects requests whose user agent does not start with `Mozilla/5.0`; the Actor
sends the standard self-identifying bot form, `Mozilla/5.0 (compatible; usda-local-food-directory/1.0; Apify Actor;
public USDA data; +https://apify.com)`, which names this Actor and hides nothing.

The Actor keeps **only what USDA publishes in that export**. Listings are businesses and organizations (market
managers, farms, hubs). The export carries no phone, email or contact-name fields, and the Actor returns none; free
text that a listing owner chose to put in the public `description` or location note is passed through unchanged, as
USDA publishes it, and is not extracted, enriched or combined with any other source. You are responsible for using the
data in line with the law, including data-protection and marketing rules (for example CAN-SPAM, TCPA, GDPR where EU
residents are concerned). This Actor is unofficial and not affiliated with, endorsed by or sponsored by the USDA.

### Support

Found a listing that parses wrongly or a field that is missing? Open an issue on the Actor page and include the
directory and `listingId`.

# Actor input Schema

## `directories` (type: `array`):

Which USDA directories to read: `farmersmarket`, `csa`, `foodhub`, `onfarmmarket` (on-farm markets and farm stands) and `agritourism`. Optional: the default is `farmersmarket`. Each directory is downloaded once per run.

## `states` (type: `array`):

Keep only listings in these states, as two-letter codes (`TX`) or names (`Texas`). The state is read from the listing's address; a listing whose address has no readable state is left out when you filter by state. Leave empty for the whole country.

## `keywords` (type: `array`):

Keep a listing when its name, description, city, products or production methods contain any of these words (case-insensitive), for example `organic`, `honey`, `eggs`. Leave empty to keep all.

## `updatedAfter` (type: `string`):

Keep only listings whose USDA record was last updated on or after this date, written YYYY-MM-DD. Listings with no update date are left out when this is set. Leave empty to keep all.

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

Stop the run after this many delivered listings. Use it as a budget cap.

## `fillMissingZip` (type: `boolean`):

About 6% of USDA addresses (11% in Texas) have a street, city and state but no ZIP. When on, those are matched in the free US Census Geocoder and the ZIP is added (`zipSource: census-geocoder`). Takes a few seconds per run; if the geocoder is down the ZIP stays null. Turn off to deliver USDA's addresses as published.

## `requestDelayMs` (type: `integer`):

Minimum wait between two requests to the portal (one request per directory). A longer Crawl-delay in the host's robots.txt always wins.

## `maxRetries` (type: `integer`):

Retries with exponential backoff for rate-limit (429), server (5xx) and network errors. Retry-After headers are honored.

## Actor input object example

```json
{
  "directories": [
    "farmersmarket"
  ],
  "states": [
    "TX",
    "OK"
  ],
  "keywords": [],
  "maxItems": 1000,
  "fillMissingZip": true,
  "requestDelayMs": 1000,
  "maxRetries": 3
}
```

# Actor output Schema

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

No description

# 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 = {
    "directories": [
        "farmersmarket"
    ],
    "states": [
        "TX",
        "OK"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("flipvo/usda-local-food-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 = {
    "directories": ["farmersmarket"],
    "states": [
        "TX",
        "OK",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("flipvo/usda-local-food-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 '{
  "directories": [
    "farmersmarket"
  ],
  "states": [
    "TX",
    "OK"
  ]
}' |
apify call flipvo/usda-local-food-directory --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,flipvo/usda-local-food-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/0FMXnJ4DV9qi9IVOP/builds/oNfmXdfaRRHiWu0RB/openapi.json
