Portal
SandboxAPI v1Not signed inExternal user: —

Shopping

Turn recipe requirements and pantry gaps into a clean, canonical shopping list your users can check off.

What you can build

“Buy what’s missing” buttons, grocery-text cleaners, meal-plan shopping trips, and MCP agents that assemble lists without double-counting pantry stock.

1. Find what is missing for recipes

Pass recipe ids (from a meal plan or a single cook intent). FoodAPI compares requirements to the current pantry partition.

missing = client.shopping.missing_ingredients(
    recipe_ids=[recipe_id],
    external_user_id="user_42",
)
for row in missing.missing_ingredients:
    print(
        row.canonical_name,
        row.required_quantity,
        row.available_quantity,
        row.missing_quantity,
        row.missing_unit,
        row.coverage_percent,
    )

2. Normalize a free-text grocery list

When users paste a list, normalize lines into canonical names, quantities, and units.

normalized = client.shopping.normalize_list(
    items=["2 lbs ground beef", "1 dozen eggs", "olive oil"],
    external_user_id="user_42",
)
for item in normalized.normalized_items:
    print(item.canonical_name, item.total_quantity, item.canonical_unit, item.ingredient_id)
{
  "normalized_items": [
    {
      "canonical_name": "ground beef",
      "total_quantity": 2.0,
      "canonical_unit": "lb",
      "ingredient_id": "…",
      "sources": ["2 lbs ground beef"]
    }
  ]
}

3. Aggregate multiple recipes

Collapse several recipes into one requirement set (useful before shopping-list persistence).

agg = client.shopping.aggregate_recipes(
    recipe_ids=recipe_ids,
    external_user_id="user_42",
)
for row in agg.ingredients:
    print(row.canonical_name, row.total_quantity, row.canonical_unit, row.sources)

4. Persist a shopping list

Stateless shopping endpoints compute needs. Use shopping_lists / shoppingLists when you need a durable checklist with revisioned item writes.

slist = client.shopping_lists.create(
    payload={
        "name": "Friday groceries",
        "status": "active",
        "source_meal_plan_id": str(plan_id) if plan_id else None,
    },
    external_user_id="user_42",
)

# Add the first missing item (example)
first = missing.missing_ingredients[0]
mutation = client.shopping_lists.items.create(
    slist.id,
    expected_revision=slist.revision,
    payload={
        "display_name": first.canonical_name,
        "ingredient_id": str(first.ingredient_id),
        "quantity": first.missing_quantity,
        "unit": first.missing_unit,
    },
    external_user_id="user_42",
)
print(mutation.item.id, mutation.revision)

Production considerations

  • Always pass the same external_user_id used for pantry so “missing” reflects that user’s stock.
  • Quantities may be null when units cannot be reconciled — show display names and let users edit.
  • MCP build_shopping_list composes missing + aggregate calls (shopping + pantry read scopes). Scopes

Next

Connect shopping to the rest of the kitchen