Build a Unified Food Picker and Submit a Mixed Manual Intake
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_ingredientinside 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
userIdfor 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:
idwhen the product is a top-levelbarcode_food_item;barcodeClassIdwhen the product is a nestedbarcode_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
dishesarray. If one item containstype, 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/USERIDThe 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 thedatafield and the optional file inimage. - 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
- GET /v2/food/types → 🔴 🔵 Discover the supported unified food types.
- GET /food/search → 🔴 🔵 Search normalized dishes, ingredients, favorites, custom recipes, and stored barcode products.
- POST /barcode_scan/{barcode_id} → 🔴 Resolve a new barcode and obtain its BarcodeClass
dish_id. - POST /intake/manualInput/{userId} → 🔴 🔵 Create one manual intake with the unified typed contract.
- POST /intake/manualInput/bulkAssign → 🔵 Create multiple manual intakes for managed APIUsers.
- POST /intake/{image_id}/display_image → ⚫ 🔴 🔵 Attach or replace a display-only image.
- DELETE /intake/{image_id}/display_image → ⚫ 🔴 🔵 Remove the explicitly attached display image.
Common Pitfalls & Tips
- Keep
typewithid: numeric IDs belong to different namespaces. - Do not submit
barcode_ingredientas a top-level item: it belongs inside another food item'singredientsarray. - Resolve barcodes first: both barcode roles use the BarcodeClass
dish_idreturned by the barcode lookup endpoint. - Do not mix request contracts in one intake: a
dishesarray 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.
Updated about 2 hours ago
