BETTERMENU
Studio

Set Recipe Ingredients

④ of 5 | Next: ⑤ addRecipeServing

Save the full ingredient list for a recipe in one commit — add, edit, delete, and reorder are all expressed as the complete ordered list you send. Each item is a structured form or resolution row; there is no free-text parsing on this path.

The list is discriminated by source: • form — a structured search → pick → quantity row. Omit id for a new row (the server mints one) or send an existing irow_... id to edit/reorder it. • resolution — an existing row born from the resolve workflow. It must carry an existing irow_... id; immutable lineage is inherited server-side and cannot be supplied by the client.

ingredients_entry_method must equal the recipe's locked entry method. A recipe locked to form rejects any resolution row.

position is taken verbatim, so drag-reorder is a resend with the new positions. An empty items list deletes all ingredients.

On success, recipe-level nutrition (L1) and any existing per-serving panels (L2) are recomputed atomically in the same commit.

Next: ⑤ addRecipeServing — add a serving size to generate the final nutrition facts label.

POST
/studio/recipes/{recipe_id}/ingredients

Path Parameters

recipe_id*Recipe Id

Header Parameters

If-Match*string

Optimistic-concurrency token. Pass the version_id returned by the previous mutation (or by GET /recipes/{id}/versions) so the server can reject the request when another writer has advanced the recipe in the meantime. A stale token (header present but HEAD has moved) surfaces HTTP 409 with the current version_id in the response body. An absent token on a versioned recipe surfaces HTTP 412. Only consumed on API versions that declare the recipe_versioning capability — older versions ignore the header.

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X POST "https://api.bettermenu.live/studio/recipes/string/ingredients" \  -H "If-Match: string" \  -H "Content-Type: application/json" \  -d '{    "ingredients_entry_method": "resolution",    "items": [      {        "position": 0,        "selected_ingredient_id": "string",        "selected_ingredient_name": "string",        "display_name": "string",        "metric_quantity_g": 0,        "search_provenance": "system_matched",        "unit_conversion_provenance": "identity"      }    ]  }'
{
  "data": {
    "recipe_id": "string",
    "resolution_id": "string",
    "ingredients": [
      {
        "id": "string",
        "position": 0,
        "selected_ingredient_id": "string",
        "selected_ingredient_name": "string",
        "display_name": "string",
        "metric_quantity_g": 0,
        "search_provenance": "system_matched",
        "unit_conversion_provenance": "identity",
        "source": "resolution",
        "original_text": "string",
        "parsed_quantity": 0,
        "parsed_unit": "string",
        "parsed_ingredient_name": "string",
        "portion_id": "string"
      }
    ],
    "ingredient_conversions": [
      {
        "conversion_source": "string",
        "reasoning": "string",
        "portion_reference": {
          "portion_bmid": "string",
          "measure_unit_name": "string",
          "gram_weight": "string",
          "amount": "string"
        }
      }
    ],
    "ingredient_resolutions": [
      {
        "original_text": "string",
        "parsed": {
          "quantity_text": "string",
          "quantity": 0,
          "unit": "string",
          "ingredient_name": "string"
        },
        "converted": {
          "quantity": 0,
          "unit": "string",
          "steps": [
            {
              "from_quantity": 0,
              "from_unit": "string",
              "to_quantity": 0,
              "to_unit": "string",
              "source": "string",
              "reasoning": "string"
            }
          ]
        },
        "matches": {
          "query_text": "string",
          "candidates": [
            {
              "ingredient_id": "string",
              "ingredient_name": "string"
            }
          ],
          "selected_match": {
            "ingredient_id": "string",
            "ingredient_name": "string"
          },
          "match_confidence": 0,
          "search_strategy_used": "standard"
        }
      }
    ],
    "updated_at": "string"
  },
  "meta": {
    "version": "string",
    "request_id": "string",
    "timestamp": "string"
  },
  "version_id": "string"
}
Empty
Empty
{
  "type": "https://api.bettermenu.com/errors/recipe_not_found",
  "title": "Recipe Not Found",
  "status": 400,
  "detail": "Recipe rcpe_abc123 does not exist",
  "code": "recipe_not_found",
  "retryable": true,
  "instance": "/api/v1/recipes/rcpe_abc123",
  "trace_id": "req_xyz789",
  "errors": [
    {}
  ],
  "invalid_ids": [
    "f_abc123",
    "f_xyz789"
  ]
}
BETTERMENU

FDA-compliant nutrition labels from a recipe, in minutes.

Visit bettermenu.live