Build a Unified Food Picker and Submit a Mixed Manual Intake

Search different food sources through one picker, combine them in a single manual intake, use barcode products as foods or ingredients, and optionally attach a display-only image.

What You’ll Build

You’ll implement a unified food-reporting flow where the client searches dishes, ingredients, favorites, custom recipes, and stored barcode products through one normalized API, keeps each selected result's type and id, and submits different food sources together in one typed manual intake.

The same flow supports a scanned barcode in two different roles:

  • as a complete top-level barcode_food_item;
  • as a nested barcode_ingredient inside another food item's recipe.

After the intake is created, the client can optionally attach a presentation-only image without triggering food recognition or changing the nutritional calculation.


Prerequisites

  • Plan: Monitor and above for manual-intake creation.
  • User type: 🔴 APIUser or 🔵 APIUserManager.
  • An active API token.
  • A target userId for the manual intake.
  • For a barcode that has not been resolved yet, the product barcode value so it can first be submitted to the barcode lookup endpoint.

Sequence Diagram

sequenceDiagram
  participant User
  participant App
  participant Food AI Engine API

  App->>Food AI Engine API: GET /v2/food/types
  Food AI Engine API-->>App: Supported unified food types
  App->>Food AI Engine API: GET /v2/food/search
  Food AI Engine API-->>App: Normalized item type + id results
  opt New barcode is not in search
    App->>Food AI Engine API: POST /v2/barcode_scan/{barcode_id}
    Food AI Engine API-->>App: dish_id for BarcodeClass
  end
  User->>App: Select and combine foods
  App->>Food AI Engine API: POST /v2/intake/manualInput/{userId}
  Food AI Engine API-->>App: 201 Created (manual intake)
  opt Add a presentation image
    App->>Food AI Engine API: POST /v2/intake/{image_id}/display_image
    Food AI Engine API-->>App: Display image attached
  end
  App-->>User: Mixed intake saved ✅

Implementation Guide

1. Discover and Search Supported Food Items

Call GET /v2/food/types to retrieve the current top-level types and determine which ones require search text.

Then call GET /food/search and preserve both item type and id for each selected result.

For example:

{
  "type": "favorite",
  "id": 701,
  "name": "Usual lunch"
}

The numeric id alone is not enough because different food namespaces can contain the same numeric value.

2. Resolve New Barcode Products When Necessary

Unified Food Search can return barcode products that are already stored in LogMeal.

If the scanned product is not available yet, call:

POST /v2/barcode_scan/{barcode_id}

The response contains dish_id, which identifies the stored BarcodeClass GeneralClass.

Use that value as:

  • id when the product is a top-level barcode_food_item;
  • barcodeClassId when the product is a nested barcode_ingredient.

3. Build One Unified Mixed Payload

Every top-level item in a unified dishes array must include type.

The following example combines a regular dish, favorite, custom recipe, barcode food item, and an ingredient-only food that itself contains both a regular ingredient and a barcode ingredient:

{
  "intake_name": "Friday lunch",
  "dishes": [
    {
      "type": "dish",
      "id": 123,
      "quantity": 250
    },
    {
      "type": "favorite",
      "id": 701
    },
    {
      "type": "custom_recipe",
      "id": 88,
      "quantity": 180
    },
    {
      "type": "barcode_food_item",
      "id": 9501,
      "quantity": 100
    },
    {
      "type": "ingredient",
      "name": "Side salad",
      "ingredients": [
        {
          "type": "ingredient",
          "ingredientId": 140,
          "ingredientAmount": 120
        },
        {
          "type": "barcode_ingredient",
          "barcodeClassId": 9502,
          "ingredientAmount": 30
        }
      ]
    }
  ]
}

Do not mix legacy food items and typed food items inside the same dishes array. If one item contains type, every item in that intake must use the unified contract.

4. Create the Manual Intake

For one APIUser, send the payload to:

POST /v2/intake/manualInput/USERID

The APIUser can create their own intake. An APIUserManager can create an intake for an APIUser they are authorized to manage.

Favorites and custom recipes are validated against the target APIUser's context:

  • the favorite must belong to that APIUser;
  • the custom recipe must belong to that APIUser's APICompany.

For manager workflows that create several intakes at once, use POST /intake/manualInput/bulkAssign. Each bulk entry requires userId, timestamp, and dishes. Different entries can independently use the legacy or unified contract, but one individual dishes array cannot mix both.

5. Optionally Attach a Display Image

A manual-intake image is display-only. It does not trigger food recognition, segmentation, barcode processing, prediction, or nutritional recalculation.

You can attach it in either of these ways:

  • At creation time: send multipart/form-data, with the manual-intake JSON serialized into the data field and the optional file in image.
  • After creation: call POST /intake/{image_id}/display_image with the image in multipart field image.

A later POST replaces the previous display image. Use DELETE /intake/{image_id}/display_image to remove it.

The maximum uploaded display-image size is 1 MB.


APIUserManager Bulk Assignment

For a bulk request, every entry has its own target user and required timestamp:

{
  "intakes": [
    {
      "userId": 11,
      "timestamp": "2026/10/07, 12:30:00",
      "occasion": "lunch",
      "dishes": [
        {
          "type": "dish",
          "id": 123,
          "quantity": 250
        },
        {
          "type": "favorite",
          "id": 701
        }
      ]
    },
    {
      "userId": 12,
      "timestamp": "2026/10/07, 12:30:00",
      "dishes": [
        {
          "type": "barcode_food_item",
          "id": 9501,
          "quantity": 100
        }
      ]
    }
  ]
}

An intake may provide either occasion or occasion_id, but not both. An explicit occasion overrides the occasion that would otherwise be inferred from the timestamp.

When the bulk endpoint is sent as multipart data with one image file, the same uploaded file is applied independently to all created intakes and each intake receives its own display-image association.


Related Endpoints


Common Pitfalls & Tips

  • Keep type with id: numeric IDs belong to different namespaces.
  • Do not submit barcode_ingredient as a top-level item: it belongs inside another food item's ingredients array.
  • Resolve barcodes first: both barcode roles use the BarcodeClass dish_id returned by the barcode lookup endpoint.
  • Do not mix request contracts in one intake: a dishes array is either entirely legacy or entirely unified.
  • Use the correct user context: favorites belong to an individual APIUser, while custom recipes are company-scoped.
  • Treat display images as presentation only: uploading or replacing one does not recalculate the intake.

Next Steps

  • Review Unified Food Search for search context, ranking, pagination, and supported item types.
  • Review Manual or Assigned Intakes for the complete manual-input contract, bulk timestamp/occasion behavior, and display-image lifecycle.
  • Review Barcode Scanner for barcode lookup and barcode-specific workflows.

Did this page help you?