# Instacart API scraper — instacart.com (United States) and instacart.ca (Canada): one grocery product's price at every retailer serving a postal code, the retailers that deliver there, and keyword search across Costco, Kroger, Safeway, Walmart, ALDI and hundreds more with was-price, loyalty-card price, promotions, unit price and stock level. No account, no browser.

> Search Instacart by keyword at a postal code. Products come back grouped by retailer, each priced at that retailer's store serving the postal code: regular price, was-price and discount, loyalty-card price, promotions such as Buy 1 get 1, unit price, weight-estimated package price, stock level including 'Likely out of stock', dietary badges and Instacart's best-seller badge. Optionally one retailer only.
> ReefAPI engine `instacart` · 4 endpoints · clean JSON, no scraping or browsers to manage.

## How to call
- **Endpoint:** `POST https://api.reefapi.com/instacart/v1/<action>` with a JSON body.
- **Auth:** header `x-api-key: <YOUR_REEFAPI_KEY>` — create one free (1,000 credits, no card): https://reefapi.com/signup
- **Response (every call):** `{ ok: boolean, data: ..., meta: { record_count, credits, ... }, error: { code, message } }` — branch on `ok`. Failed calls are free except verified SHEIN NOT_FOUND on product/detail and price (4 credits).
- **One key + one shared credit pool** across every ReefAPI API. Per-call credits are listed on each endpoint below.
- **Use it from an AI agent (MCP):** connect `https://api.reefapi.com/mcp` (remote streamable-http, `Authorization: Bearer <key>`) and your assistant can call these actions directly.

## Endpoints

### POST /instacart/v1/search — 2 credits
Search Instacart by keyword at a postal code. Products come back grouped by retailer, each priced at that retailer's store serving the postal code: regular price, was-price and discount, loyalty-card price, promotions such as Buy 1 get 1, unit price, weight-estimated package price, stock level including 'Likely out of stock', dietary badges and Instacart's best-seller badge. Optionally one retailer only.

**Parameters:**
- `query` (string, required) — What to look for, as a shopper would type it on Instacart ('milk', 'ben jerrys').
- `postal_code` (string, required) — The delivery postal code prices and stores are for: a 5-digit ZIP for us (90210), a Canadian postal code for ca (M5V 3L9).
- `country` (enum, optional, default "us") — Which Instacart: us (instacart.com, USD, 5-digit ZIP) or ca (instacart.ca, CAD, postal code like M5V 3L9). [one of: us, ca]
- `retailer` (string, optional) — Optional: one retailer, by the `slug` or `retailer_id` that `retailers` returns for the same postal code (costco, ralphs, walmart, aldi …).
- `max_retailers` (integer, optional, default 5) — How many retailers' products to return per page (1-20, default 5), in a fixed order by Instacart retailer id so pages never overlap. Each retailer is priced at its own store.
- `per_retailer` (integer, optional, default 10) — Products per retailer (1-40, default 10). Instacart returns at most 40 matches per retailer for one query.
- `page` (integer, optional, default 1) — Page of retailers (1-based): page 2 returns the next `max_retailers` retailers. Read `retailers_total` and `has_more`.
- `include_unmatched` (boolean, optional, default false) — Instacart fills no-match queries with unrelated products and does not flag them. false (default) drops rows that share no word with the query and counts them in `unmatched_dropped`; true keeps every row.
- `max_rotations` (integer, optional, default 3) — Advanced: how many times to retry a difficult request (1-6, default 3).

**Returns:** groups[]{retailer{retailer_id,slug,name,shop_id,service_type,retailer_location_id}, items_total (matches at this retailer, ≤40), items[]{product_id, item_id, name, brand (lower-cased as Instacart publishes it on item rows), size, url, image, currency, price (the retailer's regular Instacart price; for weight-estimated packages the estimated package price), price_display, original_price (was-price, only when on sale), discount_percent, savings, loyalty_price (the with-loyalty-card price, when lower), loyalty_label, loyalty_card_offer, unit_price_text ('$0.09/fl oz', '$8.97 / lb'), size_text, price_is_estimate, estimated_weight_text, promotions[]{label ('Buy 1, get 1 free', 'Spend $20, save $4', 'Rollback'),type,coupon}, deal_terms ('Add 2 to qualify for deal', 'Sale ends in 2 days'), great_price, available, stock_level ('many_in_stock'|'in_stock'|'likely_out_of_stock'|'out_of_stock'), stock_label, likely_out_of_stock, sold_by ('each'|'weight'), weight_option, par_weight{quantity,unit}, max_order_quantity, on_sale, buy_one_get_one, coupon_available, store_brand, dietary[], nutrition_highlights[]{name,value,unit}, variant_values[], retailer_reference_code, retailer_lookup_code, badge ('Best seller'), retailer{retailer_id,slug,name,shop_id,service_type,retailer_location_id}}}, count, retailers_total, page, has_more, unmatched_dropped, query, location{country,postal_code,place,zone_id,latitude,longitude}

**Example request body:**
```json
{
  "query": "milk",
  "postal_code": "90210"
}
```

### POST /instacart/v1/product/detail — 1 credit
One Instacart product: name, brand, size, category and breadcrumb, description, ingredients, directions, warnings, every image, rating and rating count — and, with a postal code, its current price at every retailer selling it there (lowest first, with was-prices). Add a retailer for that store's full item record: stock level, unit price, loyalty price and promotions.

**Parameters:**
- `product_id` (string, required) — Instacart product id (every row's `product_id`) or an instacart.com / instacart.ca /products/ URL.
- `postal_code` (string, optional) — Optional. With a postal code the product comes back with its price at every retailer that sells it there (`offers`). Without one, only the product record and Instacart's national 'starting at' price.
- `country` (enum, optional, default "us") — Which Instacart: us (instacart.com, USD, 5-digit ZIP) or ca (instacart.ca, CAD, postal code like M5V 3L9). [one of: us, ca]
- `retailer` (string, optional) — Optional: one retailer, by the `slug` or `retailer_id` that `retailers` returns for the same postal code (costco, ralphs, walmart, aldi …).
- `max_rotations` (integer, optional, default 3) — Advanced: how many times to retry a difficult request (1-6, default 3).

**Returns:** product{product_id, name, brand, size, category{id,name}, url, description, details, ingredients, directions, nutrition_text, warnings, other_sections[]{title,text}, images[], breadcrumb[]{name,url}, rating (0-5), rating_count, rating_scale, rating_distribution{5..1}, starting_price (Instacart's national 'starting at' price, no postal code), starting_price_note, currency}, offers[]{retailer_id,slug,name,shop_id,service_types[],price,original_price,discount_percent,discount_text,loyalty_price,price_unit,source ('multi_shop' | 'store_item' — the store's own price for a retailer Instacart's comparison list left out, identical to that store's search row),comparable (false = the store sells a different pack, e.g. a 12-jar case),item_name,item_size,pack_count,price_each (set when a price far above the rest was checked against the store's item)} (lowest price first; only with postal_code), offer_price_unit ('lb' for a product Instacart sells by weight: every offer price is then per lb, not per package; null = per item), offer_count, lowest_price, highest_price (both over comparable offers only), offers_not_comparable, retailers_carrying, retailer_item{product_id, item_id, name, brand (lower-cased as Instacart publishes it on item rows), size, url, image, currency, price (the retailer's regular Instacart price; for weight-estimated packages the estimated package price), price_display, original_price (was-price, only when on sale), discount_percent, savings, loyalty_price (the with-loyalty-card price, when lower), loyalty_label, loyalty_card_offer, unit_price_text ('$0.09/fl oz', '$8.97 / lb'), size_text, price_is_estimate, estimated_weight_text, promotions[]{label ('Buy 1, get 1 free', 'Spend $20, save $4', 'Rollback'),type,coupon}, deal_terms ('Add 2 to qualify for deal', 'Sale ends in 2 days'), great_price, available, stock_level ('many_in_stock'|'in_stock'|'likely_out_of_stock'|'out_of_stock'), stock_label, likely_out_of_stock, sold_by ('each'|'weight'), weight_option, par_weight{quantity,unit}, max_order_quantity, on_sale, buy_one_get_one, coupon_available, store_brand, dietary[], nutrition_highlights[]{name,value,unit}, variant_values[], retailer_reference_code, retailer_lookup_code, badge ('Best seller'), retailer{retailer_id,slug,name,shop_id,service_type,retailer_location_id}} (only with retailer), location{country,postal_code,place,zone_id,latitude,longitude}

**Example request body:**
```json
{
  "product_id": "81103",
  "postal_code": "90210"
}
```

### POST /instacart/v1/retailers — 1 credit
The retailers Instacart serves at a postal code — supermarkets, warehouse clubs, pharmacies, liquor, pet and specialty stores — with their slug and id for search and product/detail, retailer type, categories, logo, and whether they deliver, offer pickup, or both.

**Parameters:**
- `postal_code` (string, required) — The delivery postal code prices and stores are for: a 5-digit ZIP for us (90210), a Canadian postal code for ca (M5V 3L9).
- `country` (enum, optional, default "us") — Which Instacart: us (instacart.com, USD, 5-digit ZIP) or ca (instacart.ca, CAD, postal code like M5V 3L9). [one of: us, ca]
- `service_type` (enum, optional, default "all") — Which kind of store service to list. [one of: all, delivery, pickup]
- `category` (string, optional) — Optional: only retailers in this Instacart category (grocery, alcohol, pets, pharmacy, office …) as shown in each retailer's `categories`.
- `max_rotations` (integer, optional, default 3) — Advanced: how many times to retry a difficult request (1-6, default 3).

**Returns:** retailers[]{retailer_id, slug, name, retailer_type, categories[], logo, storefront_url, services[]{service_type,shop_id,retailer_location_id}}, count, location{country,postal_code,place,zone_id,latitude,longitude}

**Example request body:**
```json
{
  "postal_code": "90210"
}
```

### POST /instacart/v1/suggest — 1 credit
Instacart search-box suggestions for a partial query at a postal code.

**Parameters:**
- `query` (string, required) — What to look for, as a shopper would type it on Instacart ('milk', 'ben jerrys').
- `postal_code` (string, required) — The delivery postal code prices and stores are for: a 5-digit ZIP for us (90210), a Canadian postal code for ca (M5V 3L9).
- `country` (enum, optional, default "us") — Which Instacart: us (instacart.com, USD, 5-digit ZIP) or ca (instacart.ca, CAD, postal code like M5V 3L9). [one of: us, ca]
- `max_rotations` (integer, optional, default 3) — Advanced: how many times to retry a difficult request (1-6, default 3).

**Returns:** suggestions[]{term,image}, query, location{country,postal_code,place,zone_id,latitude,longitude}

**Example request body:**
```json
{
  "query": "milk",
  "postal_code": "90210"
}
```

## More
- Try it live, no code: https://reefapi.com/playground?engine=instacart
- Human docs page: https://reefapi.com/docs/instacart
- Overview page: https://reefapi.com/instacart-api
- Every ReefAPI API in one file (for your AI): https://reefapi.com/llms-full.txt
