Meals and diary API
Log meals from a draft, a photo, text or a barcode; read and fix the diary. 21 ops: log_meal, get_day, get_meal, list_review_queue, update_meal, confirm_ingredients, reject_ingredients, add_ingredients, set_nutrients, mark_reviewed, delete_meal, copy_meal, save_personal_food, list_personal_foods, ar
Log meals from a draft, a photo, text or a barcode; read and fix the diary.
log_meal
writeLog a meal · POST /api/v1/log_meal · scope meals:write
Log what the person ate. It is always stored; nothing is lost when you are unsure. In chat, YOU look at the photo or read the message and send items (a draft): free and unlimited. Other inputs, exactly one per call: text (simple text is free; anything harder is one analysis on our model, Plus), image (our model reads a photo, one analysis, Plus: in chat read it yourself instead), barcode (+ servings; free), recipe (ingredients and amounts of a home dish; free). How to fill the draft: - One item per distinct food or drink ("grilled chicken breast", "white rice", "oat milk latte"), named as a menu or label would. A dish served as one thing (a burrito, a sandwich, a smoothie) is one item; its parts are its ingredients. - Amount: grams when you can estimate them (plate ≈ 26 cm, fork ≈ 19 cm, palm of meat ≈ 100 g, fist of rice ≈ 150 g), else portion {quantity, unit} ("1 cup", "2 slice", "1 medium"). For a package with a barcode, send barcode and servings instead. - confidence (what it is) and portion_confidence (how much), 0–1: 0.9+ you can see it clearly or were told; 0.7 typical guess; 0.5 hard to tell. - ingredients: what is in the item, each with a confidence, most certain first. · ≥ 0.85: visible or stated ("eggs" in scrambled eggs). · 0.60–0.85: standard for this dish though not visible (cheese in a quesadilla, rice in sushi). · 0.30–0.60: plausible but hidden. Always consider cooking fat (butter, oil), added sugar, onion and garlic in sauces, dressings and soups: they matter for calories and for gut symptoms, and the person is asked about them. · below 0.30: leave it out. - cooking_method when you can tell (grilled, pan_fried, roasted…). Copy any label or ingredient list text you can read into label_text, verbatim. - Never invent a barcode or brand you cannot read. Examples: 1. Photo of scrambled eggs on toast with a coffee → {"items":[{"name":"scrambled eggs","grams":120,"confidence":0.9,"portion_confidence":0.7,"cooking_method":"pan_fried","ingredients":[{"name":"eggs","confidence":0.97,"visible":true},{"name":"butter","confidence":0.5},{"name":"milk","confidence":0.35}]},{"name":"sourdough toast","portion":{"quantity":1,"unit":"slice"},"confidence":0.85,"ingredients":[{"name":"wheat flour","confidence":0.9}]},{"name":"black coffee","grams":240,"confidence":0.8}]} 2. "Chipotle bowl: chicken, white rice, black beans, mild salsa, no cheese" → {"title":"Chipotle bowl","items":[{"name":"chicken burrito bowl","grams":520,"confidence":0.85,"portion_confidence":0.6,"ingredients":[{"name":"chicken","confidence":0.95},{"name":"white rice","confidence":0.95},{"name":"black beans","confidence":0.95},{"name":"tomato salsa","confidence":0.9},{"name":"onion","confidence":0.7},{"name":"garlic","confidence":0.45}]}]} 3. A granola bar wrapper with a readable barcode → {"items":[{"name":"granola bar","barcode":"0123456789012","servings":1}]} We match each item against the person's own foods, the barcode index, the food database, then recipes; portion it; compute calories and nutrients; and tier ingredients: confirmed (≥0.85 or from a label), likely (0.60–0.85, counted), possible (0.30–0.60, not counted, asked about). needs_review is set under the person's threshold (default 0.80), when a possible ingredient is one of their triggers or allergens, or when a portion is over 3× typical. Returns the meal: meal_id, needs_review and review_reasons, confidence, totals (integers: energy in millikilocalories, macros in milligrams; totals_display has kcal and grams), items with their match, grams, nutrients and ingredients by tier (each with an id and status), up to three questions (each with the ingredient id to confirm or reject) and reply: say it to the person as is. Then answer with confirm_ingredients / reject_ingredients by id; fix amounts with update_meal.
curl -X POST https://getgutlog.com/api/v1/log_meal \ -H "Authorization: Bearer gutlog_…"npx @meterapp/gutlog log_meal{
"type": "object",
"properties": {
"items": {
"minItems": 1,
"maxItems": 30,
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "The item as it would appear on a menu or label: \"grilled chicken breast\", \"white rice\", \"oat milk latte\"."
},
"food_id": {
"description": "A food id from search_foods or a previous log_meal reply, when you already know the match.",
"type": "string",
"maxLength": 100
},
"barcode": {
"description": "Barcode digits if you can read them (UPC/EAN); the product's label then wins over estimates.",
"type": "string",
"maxLength": 20
},
"brand": {
"description": "Brand when packaging shows it.",
"type": "string",
"maxLength": 120
},
"grams": {
"description": "Your estimate of the edible weight in grams. Prefer this when you can estimate it from the photo (plate size, utensils, hand).",
"type": "number",
"exclusiveMinimum": 0,
"maximum": 5000
},
"portion": {
"description": "Instead of grams: \"2 slices\", \"1 cup\", \"1 medium\".",
"type": "object",
"properties": {
"quantity": {
"type": "number",
"exclusiveMinimum": 0,
"maximum": 100
},
"unit": {
"type": "string",
"minLength": 1,
"maxLength": 40,
"description": "A household measure: \"cup\", \"slice\", \"medium\", \"tbsp\", \"oz\", \"bowl\", \"can\"."
}
},
"required": [
"quantity",
"unit"
]
},
"servings": {
"description": "Number of the product's servings (with a barcode).",
"type": "number",
"exclusiveMinimum": 0,
"maximum": 20
},
"confidence": {
"description": "How sure you are of what this item is, 0–1.",
"type": "number",
"minimum": 0,
"maximum": 1
},
"portion_confidence": {
"description": "How sure you are of the amount, 0–1.",
"type": "number",
"minimum": 0,
"maximum": 1
},
"cooking_method": {
"type": "string",
"enum": [
"raw",
"boiled",
"steamed",
"grilled",
"roasted",
"baked",
"sauteed",
"pan_fried",
"deep_fried",
"air_fried",
"braised",
"stewed",
"microwaved",
"smoked",
"unknown"
]
},
"ingredients": {
"default": [],
"description": "Ingredients with a confidence each, most certain first. Include hidden fats, sugars, onion and garlic when plausible.",
"maxItems": 40,
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
"maxLength": 120,
"description": "One ingredient as a person would say it: \"garlic\", \"olive oil\", \"cheddar\"."
},
"confidence": {
"type": "number",
"minimum": 0,
"maximum": 1,
"description": "How sure you are it is in this item, 0–1. ≥0.85 you can see or were told it; 0.60–0.85 very likely for this dish; 0.30–0.60 plausible but hidden (cooking fat, butter, sugar, garlic, onion in a sauce): include these, they are asked about. Omit below 0.30."
},
"visible": {
"description": "You can see it in the photo (true) or inferred it from the dish (false).",
"type": "boolean"
},
"ingredient_id": {
"description": "Canonical ingredient id if you know it from a previous response; otherwise leave it out.",
"type": "string",
"maxLength": 80
}
},
"required": [
"name",
"confidence"
]
}
},
"label_text": {
"description": "Any ingredient list or nutrition label text you can read, verbatim.",
"type": "string",
"maxLength": 4000
}
},
"required": [
"name"
]
},
"description": "One entry per distinct food or drink on the plate or in the message."
},
"meal_type": {
"description": "Leave out to infer from the time.",
"type": "string",
"enum": [
"breakfast",
"lunch",
"dinner",
"snack",
"drink"
]
},
"eaten_at": {
"description": "When it was eaten, ISO 8601 with offset; default now.",
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
},
"title": {
"description": "A short name for the meal: \"Chipotle bowl\", \"Breakfast\".",
"type": "string",
"maxLength": 200
},
"note": {
"description": "Anything the person said that is not an item (where, how they felt).",
"type": "string",
"maxLength": 2000
},
"client": {
"description": "Your client name, e.g. \"chatgpt\", \"claude\", \"cursor\".",
"type": "string",
"maxLength": 60
},
"model": {
"description": "Your model name, for calibration.",
"type": "string",
"maxLength": 80
},
"text": {
"description": "What the person said they ate, when you cannot send items: \"chipotle bowl, chicken, white rice, no beans\". Simple text is matched for free; anything else costs one analysis.",
"type": "string",
"minLength": 1,
"maxLength": 4000
},
"image": {
"anyOf": [
{
"type": "object",
"properties": {
"data": {
"type": "string",
"minLength": 100,
"maxLength": 10000000,
"description": "Base64 image bytes, no data: prefix."
},
"media_type": {
"type": "string",
"enum": [
"image/jpeg",
"image/png",
"image/webp",
"image/gif"
]
}
},
"required": [
"data",
"media_type"
]
},
{
"type": "object",
"properties": {
"url": {
"type": "string",
"maxLength": 2000,
"format": "starts_with",
"pattern": "^https:\\/\\/.*",
"description": "An https URL of the photo."
}
},
"required": [
"url"
]
}
],
"description": "A meal photo our server analyzes (web, CLI, API, email). Costs one analysis (Plus). In chat, read the photo yourself and send items instead: that is free."
},
"barcode": {
"description": "A barcode (UPC/EAN digits) on its own: the product label wins. Free.",
"type": "string",
"pattern": "^\\d{8,14}$"
},
"servings": {
"description": "Servings of the barcode product.",
"type": "number",
"exclusiveMinimum": 0,
"maximum": 20
},
"recipe": {
"description": "A home recipe: its ingredients and amounts, decomposed into nutrients. Free.",
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "The dish: \"Mum's lentil soup\"."
},
"ingredients": {
"minItems": 1,
"maxItems": 60,
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
"maxLength": 120
},
"grams": {
"type": "number",
"exclusiveMinimum": 0,
"maximum": 10000
},
"portion": {
"type": "object",
"properties": {
"quantity": {
"type": "number",
"exclusiveMinimum": 0,
"maximum": 1000
},
"unit": {
"type": "string",
"minLength": 1,
"maxLength": 40
}
},
"required": [
"quantity",
"unit"
]
}
},
"required": [
"name"
]
},
"description": "Everything that went into the whole pot or tray, with amounts."
},
"servings_made": {
"default": 1,
"description": "How many servings the recipe makes.",
"type": "number",
"exclusiveMinimum": 0,
"maximum": 100
},
"servings_eaten": {
"default": 1,
"description": "How many of those servings were eaten.",
"type": "number",
"exclusiveMinimum": 0,
"maximum": 20
}
},
"required": [
"name",
"ingredients"
]
}
}
}get_day
readGet a diary day · POST /api/v1/get_day · scope meals:read or GET …?input=
One day of the food diary in the person's timezone: every meal with its items (match, grams, nutrients, confidence) and ingredients (id, tier, confidence, status), the day's nutrient totals, the person's daily goals and how many meals need review. Nutrients are integers: energy in millikilocalories, macros and most minerals in milligrams, some vitamins in micrograms; totals_display and goals_display have kcal and grams. Defaults to today.
curl -X POST https://getgutlog.com/api/v1/get_day \ -H "Authorization: Bearer gutlog_…"npx @meterapp/gutlog get_day{
"type": "object",
"properties": {
"date": {
"type": "string",
"format": "date",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
"description": "A calendar day, YYYY-MM-DD, in the person's timezone."
}
}
}get_meal
readGet a meal · POST /api/v1/get_meal · scope meals:read or GET …?input=
One logged meal with everything an edit needs: item ids, ingredient ids, tiers, statuses and the open questions. Returns the meal: meal_id, needs_review and review_reasons, confidence, totals (integers: energy in millikilocalories, macros in milligrams; totals_display has kcal and grams), items with their match, grams, nutrients and ingredients by tier (each with an id and status), up to three questions (each with the ingredient id to confirm or reject) and reply: say it to the person as is.
curl -X POST https://getgutlog.com/api/v1/get_meal \ -H "Authorization: Bearer gutlog_…" \ -H "Content-Type: application/json" \ -d '{"meal_id":"6f1c2e0a-4b7d-4c2e-9a51-0d3b8e7f9a10"}'npx @meterapp/gutlog get_meal --json '{"meal_id":"6f1c2e0a-4b7d-4c2e-9a51-0d3b8e7f9a10"}'{
"type": "object",
"properties": {
"meal_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Meal id (meals.id)."
}
},
"required": [
"meal_id"
]
}List meals to review · POST /api/v1/list_review_queue · scope meals:read or GET …?input=
Meals flagged needs_review (low confidence, a possible trigger or allergen, an implausible portion), newest first, each as a full meal with its questions. Go through them with the person: ask the questions, then confirm_ingredients / reject_ingredients, update_meal for amounts, or mark_reviewed when a meal looks right.
curl -X POST https://getgutlog.com/api/v1/list_review_queue \ -H "Authorization: Bearer gutlog_…"npx @meterapp/gutlog list_review_queue{
"type": "object",
"properties": {
"limit": {
"default": 50,
"description": "Page size, at most 200.",
"type": "integer",
"minimum": 1,
"maximum": 200
},
"offset": {
"default": 0,
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
}
}update_meal
writeUpdate a meal · POST /api/v1/update_meal · scope meals:write
Change a logged meal: its title, type, time or note; per item (meal_item_id from the meal) its grams or household portion ("the chicken was more like 200 g"), its match (food_id from search_foods: "it was oat milk"), its name, or remove it; add_items with the same item shape as log_meal. Edits win over estimates and are kept as corrections. To change a single number directly use set_nutrients. Returns the meal: meal_id, needs_review and review_reasons, confidence, totals (integers: energy in millikilocalories, macros in milligrams; totals_display has kcal and grams), items with their match, grams, nutrients and ingredients by tier (each with an id and status), up to three questions (each with the ingredient id to confirm or reject) and reply: say it to the person as is.
curl -X POST https://getgutlog.com/api/v1/update_meal \ -H "Authorization: Bearer gutlog_…" \ -H "Content-Type: application/json" \ -d '{"meal_id":"6f1c2e0a-4b7d-4c2e-9a51-0d3b8e7f9a10"}'npx @meterapp/gutlog update_meal --json '{"meal_id":"6f1c2e0a-4b7d-4c2e-9a51-0d3b8e7f9a10"}'{
"type": "object",
"properties": {
"meal_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Meal id (meals.id)."
},
"title": {
"anyOf": [
{
"type": "string",
"maxLength": 200
},
{
"type": "null"
}
]
},
"meal_type": {
"type": "string",
"enum": [
"breakfast",
"lunch",
"dinner",
"snack",
"drink"
]
},
"eaten_at": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
},
"note": {
"anyOf": [
{
"type": "string",
"maxLength": 2000
},
{
"type": "null"
}
]
},
"items": {
"maxItems": 30,
"type": "array",
"items": {
"type": "object",
"properties": {
"meal_item_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Meal item id (meal_items.id)."
},
"grams": {
"type": "number",
"exclusiveMinimum": 0,
"maximum": 5000
},
"portion": {
"type": "object",
"properties": {
"quantity": {
"type": "number",
"exclusiveMinimum": 0,
"maximum": 100
},
"unit": {
"type": "string",
"minLength": 1,
"maxLength": 40
}
},
"required": [
"quantity",
"unit"
]
},
"food_id": {
"description": "Swap the match to this canonical food.",
"type": "string",
"minLength": 1,
"maxLength": 100
},
"name": {
"type": "string",
"minLength": 1,
"maxLength": 200
},
"remove": {
"type": "boolean"
}
},
"required": [
"meal_item_id"
]
}
},
"add_items": {
"maxItems": 20,
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "The item as it would appear on a menu or label: \"grilled chicken breast\", \"white rice\", \"oat milk latte\"."
},
"food_id": {
"description": "A food id from search_foods or a previous log_meal reply, when you already know the match.",
"type": "string",
"maxLength": 100
},
"barcode": {
"description": "Barcode digits if you can read them (UPC/EAN); the product's label then wins over estimates.",
"type": "string",
"maxLength": 20
},
"brand": {
"description": "Brand when packaging shows it.",
"type": "string",
"maxLength": 120
},
"grams": {
"description": "Your estimate of the edible weight in grams. Prefer this when you can estimate it from the photo (plate size, utensils, hand).",
"type": "number",
"exclusiveMinimum": 0,
"maximum": 5000
},
"portion": {
"description": "Instead of grams: \"2 slices\", \"1 cup\", \"1 medium\".",
"type": "object",
"properties": {
"quantity": {
"type": "number",
"exclusiveMinimum": 0,
"maximum": 100
},
"unit": {
"type": "string",
"minLength": 1,
"maxLength": 40,
"description": "A household measure: \"cup\", \"slice\", \"medium\", \"tbsp\", \"oz\", \"bowl\", \"can\"."
}
},
"required": [
"quantity",
"unit"
]
},
"servings": {
"description": "Number of the product's servings (with a barcode).",
"type": "number",
"exclusiveMinimum": 0,
"maximum": 20
},
"confidence": {
"description": "How sure you are of what this item is, 0–1.",
"type": "number",
"minimum": 0,
"maximum": 1
},
"portion_confidence": {
"description": "How sure you are of the amount, 0–1.",
"type": "number",
"minimum": 0,
"maximum": 1
},
"cooking_method": {
"type": "string",
"enum": [
"raw",
"boiled",
"steamed",
"grilled",
"roasted",
"baked",
"sauteed",
"pan_fried",
"deep_fried",
"air_fried",
"braised",
"stewed",
"microwaved",
"smoked",
"unknown"
]
},
"ingredients": {
"default": [],
"description": "Ingredients with a confidence each, most certain first. Include hidden fats, sugars, onion and garlic when plausible.",
"maxItems": 40,
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
"maxLength": 120,
"description": "One ingredient as a person would say it: \"garlic\", \"olive oil\", \"cheddar\"."
},
"confidence": {
"type": "number",
"minimum": 0,
"maximum": 1,
"description": "How sure you are it is in this item, 0–1. ≥0.85 you can see or were told it; 0.60–0.85 very likely for this dish; 0.30–0.60 plausible but hidden (cooking fat, butter, sugar, garlic, onion in a sauce): include these, they are asked about. Omit below 0.30."
},
"visible": {
"description": "You can see it in the photo (true) or inferred it from the dish (false).",
"type": "boolean"
},
"ingredient_id": {
"description": "Canonical ingredient id if you know it from a previous response; otherwise leave it out.",
"type": "string",
"maxLength": 80
}
},
"required": [
"name",
"confidence"
]
}
},
"label_text": {
"description": "Any ingredient list or nutrition label text you can read, verbatim.",
"type": "string",
"maxLength": 4000
}
},
"required": [
"name"
]
}
},
"from_card": {
"description": "Set only by the meal card UI: the person tapped it. Agents leave it out.",
"type": "boolean"
}
},
"required": [
"meal_id"
]
}confirm_ingredients
writeConfirm ingredients · POST /api/v1/confirm_ingredients · scope meals:write
The person says these ingredients were in the meal ("yes, butter"): ids from the meal's ingredients or questions, or all_likely: true with meal_id to confirm every likely one at once ("yes, that's right"). A confirmed possible ingredient starts counting (a hidden fat or sugar adds its calories). Returns the meal: meal_id, needs_review and review_reasons, confidence, totals (integers: energy in millikilocalories, macros in milligrams; totals_display has kcal and grams), items with their match, grams, nutrients and ingredients by tier (each with an id and status), up to three questions (each with the ingredient id to confirm or reject) and reply: say it to the person as is.
curl -X POST https://getgutlog.com/api/v1/confirm_ingredients \ -H "Authorization: Bearer gutlog_…"npx @meterapp/gutlog confirm_ingredients{
"type": "object",
"properties": {
"meal_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Meal id (meals.id)."
},
"ids": {
"description": "Ingredient ids (meal_item_ingredients.id).",
"maxItems": 100,
"type": "array",
"items": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
}
},
"all_likely": {
"type": "boolean"
},
"from_card": {
"description": "Set only by the meal card UI: the person tapped it. Agents leave it out.",
"type": "boolean"
}
}
}reject_ingredients
writeReject ingredients · POST /api/v1/reject_ingredients · scope meals:write
The person says these ingredients were not in the meal ("no cheese", "no garlic"): ids from the meal's ingredients or questions. A rejected ingredient no longer counts toward nutrients or gut exposures. If the numbers should drop too (the cheese was part of the matched dish), also set_nutrients or swap the match with update_meal. Returns the meal: meal_id, needs_review and review_reasons, confidence, totals (integers: energy in millikilocalories, macros in milligrams; totals_display has kcal and grams), items with their match, grams, nutrients and ingredients by tier (each with an id and status), up to three questions (each with the ingredient id to confirm or reject) and reply: say it to the person as is.
curl -X POST https://getgutlog.com/api/v1/reject_ingredients \ -H "Authorization: Bearer gutlog_…" \ -H "Content-Type: application/json" \ -d '{"ids":["6f1c2e0a-4b7d-4c2e-9a51-0d3b8e7f9a10"]}'npx @meterapp/gutlog reject_ingredients --json '{"ids":["6f1c2e0a-4b7d-4c2e-9a51-0d3b8e7f9a10"]}'{
"type": "object",
"properties": {
"meal_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Meal id (meals.id)."
},
"ids": {
"minItems": 1,
"maxItems": 100,
"type": "array",
"items": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
"description": "Ingredient ids (meal_item_ingredients.id)."
},
"from_card": {
"description": "Set only by the meal card UI: the person tapped it. Agents leave it out.",
"type": "boolean"
}
},
"required": [
"ids"
]
}add_ingredients
writeAdd ingredients · POST /api/v1/add_ingredients · scope meals:write
Add ingredients we missed to one item ("it had onions", "cooked in butter"), confirmed. A hidden fat or sugar adds its usual amount's calories. Returns the meal: meal_id, needs_review and review_reasons, confidence, totals (integers: energy in millikilocalories, macros in milligrams; totals_display has kcal and grams), items with their match, grams, nutrients and ingredients by tier (each with an id and status), up to three questions (each with the ingredient id to confirm or reject) and reply: say it to the person as is.
curl -X POST https://getgutlog.com/api/v1/add_ingredients \ -H "Authorization: Bearer gutlog_…" \ -H "Content-Type: application/json" \ -d '{"meal_item_id":"6f1c2e0a-4b7d-4c2e-9a51-0d3b8e7f9a10","ingredients":[{"name":"scrambled eggs"}]}'npx @meterapp/gutlog add_ingredients --json '{"meal_item_id":"6f1c2e0a-4b7d-4c2e-9a51-0d3b8e7f9a10","ingredients":[{"name":"scrambled eggs"}]}'{
"type": "object",
"properties": {
"meal_item_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Meal item id (meal_items.id)."
},
"ingredients": {
"minItems": 1,
"maxItems": 20,
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
"maxLength": 120
},
"ingredient_id": {
"type": "string",
"maxLength": 100
}
},
"required": [
"name"
]
}
},
"from_card": {
"description": "Set only by the meal card UI: the person tapped it. Agents leave it out.",
"type": "boolean"
}
},
"required": [
"meal_item_id",
"ingredients"
]
}set_nutrients
writeSet nutrient values · POST /api/v1/set_nutrients · scope meals:write
Override nutrient values of one item directly ("the protein was more like 45 g", "it said 320 kcal"). Values are in display units: kcal for energy, grams for macros, mg for sodium and other minerals, µg (ug) for vitamins A, D, B12 and folate, unless unit says otherwise. Each field set is locked: later portion changes keep it. value null unlocks a field back to the estimate. Returns the meal: meal_id, needs_review and review_reasons, confidence, totals (integers: energy in millikilocalories, macros in milligrams; totals_display has kcal and grams), items with their match, grams, nutrients and ingredients by tier (each with an id and status), up to three questions (each with the ingredient id to confirm or reject) and reply: say it to the person as is.
curl -X POST https://getgutlog.com/api/v1/set_nutrients \ -H "Authorization: Bearer gutlog_…" \ -H "Content-Type: application/json" \ -d '{"meal_item_id":"6f1c2e0a-4b7d-4c2e-9a51-0d3b8e7f9a10","set":[{"nutrient":"nutrient","value":0}]}'npx @meterapp/gutlog set_nutrients --json '{"meal_item_id":"6f1c2e0a-4b7d-4c2e-9a51-0d3b8e7f9a10","set":[{"nutrient":"nutrient","value":0}]}'{
"type": "object",
"properties": {
"meal_item_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Meal item id (meal_items.id)."
},
"set": {
"minItems": 1,
"maxItems": 20,
"type": "array",
"items": {
"type": "object",
"properties": {
"nutrient": {
"type": "string",
"minLength": 1,
"maxLength": 40,
"description": "A nutrient key: energy, protein, carbs, fat, fiber, sugar, added_sugar, sodium, saturated_fat…"
},
"value": {
"anyOf": [
{
"type": "number",
"minimum": 0,
"maximum": 100000
},
{
"type": "null"
}
]
},
"unit": {
"type": "string",
"enum": [
"kcal",
"g",
"mg",
"ug"
]
}
},
"required": [
"nutrient",
"value"
]
}
},
"from_card": {
"description": "Set only by the meal card UI: the person tapped it. Agents leave it out.",
"type": "boolean"
}
},
"required": [
"meal_item_id",
"set"
]
}mark_reviewed
writeMark a meal reviewed · POST /api/v1/mark_reviewed · scope meals:write
The person looked at the meal and it is right as it is: it leaves the review queue and stays out. Returns the meal: meal_id, needs_review and review_reasons, confidence, totals (integers: energy in millikilocalories, macros in milligrams; totals_display has kcal and grams), items with their match, grams, nutrients and ingredients by tier (each with an id and status), up to three questions (each with the ingredient id to confirm or reject) and reply: say it to the person as is.
curl -X POST https://getgutlog.com/api/v1/mark_reviewed \ -H "Authorization: Bearer gutlog_…" \ -H "Content-Type: application/json" \ -d '{"meal_id":"6f1c2e0a-4b7d-4c2e-9a51-0d3b8e7f9a10"}'npx @meterapp/gutlog mark_reviewed --json '{"meal_id":"6f1c2e0a-4b7d-4c2e-9a51-0d3b8e7f9a10"}'{
"type": "object",
"properties": {
"meal_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Meal id (meals.id)."
},
"from_card": {
"description": "Set only by the meal card UI: the person tapped it. Agents leave it out.",
"type": "boolean"
}
},
"required": [
"meal_id"
]
}delete_meal
writeDelete a meal · POST /api/v1/delete_meal · scope meals:write
Remove a meal from the diary (logged twice, or never eaten). It no longer counts toward totals, the review queue or gut analysis. Confirm with the person first.
curl -X POST https://getgutlog.com/api/v1/delete_meal \ -H "Authorization: Bearer gutlog_…" \ -H "Content-Type: application/json" \ -d '{"meal_id":"6f1c2e0a-4b7d-4c2e-9a51-0d3b8e7f9a10"}'npx @meterapp/gutlog delete_meal --json '{"meal_id":"6f1c2e0a-4b7d-4c2e-9a51-0d3b8e7f9a10"}'{
"type": "object",
"properties": {
"meal_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Meal id (meals.id)."
},
"from_card": {
"description": "Set only by the meal card UI: the person tapped it. Agents leave it out.",
"type": "boolean"
}
},
"required": [
"meal_id"
]
}copy_meal
writeCopy a meal · POST /api/v1/copy_meal · scope meals:write
Log a meal again ("same as yesterday's breakfast"): meal_id, or date + meal_type for the latest meal of that type that day. Logged now unless eaten_at is given; as_meal_type changes its type. Free. Returns the meal: meal_id, needs_review and review_reasons, confidence, totals (integers: energy in millikilocalories, macros in milligrams; totals_display has kcal and grams), items with their match, grams, nutrients and ingredients by tier (each with an id and status), up to three questions (each with the ingredient id to confirm or reject) and reply: say it to the person as is.
curl -X POST https://getgutlog.com/api/v1/copy_meal \ -H "Authorization: Bearer gutlog_…"npx @meterapp/gutlog copy_meal{
"type": "object",
"properties": {
"meal_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Meal id (meals.id)."
},
"date": {
"type": "string",
"format": "date",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
"description": "A calendar day, YYYY-MM-DD, in the person's timezone."
},
"meal_type": {
"type": "string",
"enum": [
"breakfast",
"lunch",
"dinner",
"snack",
"drink"
]
},
"eaten_at": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
},
"as_meal_type": {
"type": "string",
"enum": [
"breakfast",
"lunch",
"dinner",
"snack",
"drink"
]
}
}
}save_personal_food
writeSave as my usual · POST /api/v1/save_personal_food · scope meals:write
Save a corrected item (meal_item_id) or a whole meal (meal_id, with a name) as one of the person's own foods: "save this as my usual latte". Next time that name matches first, with these exact nutrients and ingredients. Saving under an existing name updates it. Items corrected on three different meals are saved automatically.
curl -X POST https://getgutlog.com/api/v1/save_personal_food \ -H "Authorization: Bearer gutlog_…"npx @meterapp/gutlog save_personal_food{
"type": "object",
"properties": {
"meal_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Meal id (meals.id)."
},
"meal_item_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Meal item id (meal_items.id)."
},
"name": {
"description": "What the person calls it; default the item's name (required for a whole meal without a title).",
"type": "string",
"minLength": 1,
"maxLength": 200
},
"aliases": {
"description": "Other names it goes by (\"my latte\", \"usual coffee\").",
"maxItems": 10,
"type": "array",
"items": {
"type": "string",
"minLength": 1,
"maxLength": 200
}
}
}
}List personal foods · POST /api/v1/list_personal_foods · scope meals:read or GET …?input=
The person's own foods (saved as usual or learned from corrections), most used first, with nutrients per 100 g, default grams and ingredients. Log one by its name, or with food_id "personal:<id>" in a log_meal item.
curl -X POST https://getgutlog.com/api/v1/list_personal_foods \ -H "Authorization: Bearer gutlog_…"npx @meterapp/gutlog list_personal_foods{
"type": "object",
"properties": {
"limit": {
"default": 50,
"description": "Page size, at most 200.",
"type": "integer",
"minimum": 1,
"maximum": 200
},
"offset": {
"default": 0,
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
}
}Archive a personal food · POST /api/v1/archive_personal_food · scope meals:write
Stop matching a personal food (it was wrong, or the person no longer eats it). Meals already logged with it keep their numbers.
curl -X POST https://getgutlog.com/api/v1/archive_personal_food \ -H "Authorization: Bearer gutlog_…" \ -H "Content-Type: application/json" \ -d '{"personal_food_id":"6f1c2e0a-4b7d-4c2e-9a51-0d3b8e7f9a10"}'npx @meterapp/gutlog archive_personal_food --json '{"personal_food_id":"6f1c2e0a-4b7d-4c2e-9a51-0d3b8e7f9a10"}'{
"type": "object",
"properties": {
"personal_food_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
}
},
"required": [
"personal_food_id"
]
}rate_analysis
writeRate an analysis · POST /api/v1/rate_analysis · scope meals:write
Thumbs up or down on how well a meal was analyzed, with an optional reason in the person's words. One rating per person per meal; rating again replaces it. Ask what was wrong after a thumbs down and fix it with the edit ops.
curl -X POST https://getgutlog.com/api/v1/rate_analysis \ -H "Authorization: Bearer gutlog_…" \ -H "Content-Type: application/json" \ -d '{"meal_id":"6f1c2e0a-4b7d-4c2e-9a51-0d3b8e7f9a10","rating":"up"}'npx @meterapp/gutlog rate_analysis --json '{"meal_id":"6f1c2e0a-4b7d-4c2e-9a51-0d3b8e7f9a10","rating":"up"}'{
"type": "object",
"properties": {
"meal_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Meal id (meals.id)."
},
"rating": {
"type": "string",
"enum": [
"up",
"down"
]
},
"reason": {
"type": "string",
"maxLength": 1000
}
},
"required": [
"meal_id",
"rating"
]
}get_calibration
readGet confidence calibration · POST /api/v1/get_calibration · scope meals:read or GET …?input=
How well ingredient confidences match what the person confirmed and rejected: per 0.1 bucket and source (photo, text, database), how many were answered, the confirm rate and the gap from the bucket's midpoint. A well calibrated 0.8 bucket confirms about 80%.
curl -X POST https://getgutlog.com/api/v1/get_calibration \ -H "Authorization: Bearer gutlog_…"npx @meterapp/gutlog get_calibration{
"type": "object",
"properties": {
"days": {
"default": 90,
"type": "integer",
"minimum": 1,
"maximum": 365
}
}
}Get calibration across everyone · POST /api/v1/get_calibration_all · scope meals:read or GET …?input=
Calibration across every organization (counts only), for tuning thresholds and the analysis prompt. Gutlog staff only.
curl -X POST https://getgutlog.com/api/v1/get_calibration_all \ -H "Authorization: Bearer gutlog_…"npx @meterapp/gutlog get_calibration_all{
"type": "object",
"properties": {
"days": {
"default": 30,
"type": "integer",
"minimum": 1,
"maximum": 365
}
}
}Refresh shared aliases · POST /api/v1/refresh_global_aliases · scope meals:write
Promote correction patterns seen from at least 20 different people to shared aliases (and drop ones that fell under). Runs daily on its own; staff only. Only the pattern and a count are kept, never who.
curl -X POST https://getgutlog.com/api/v1/refresh_global_aliases \ -H "Authorization: Bearer gutlog_…"npx @meterapp/gutlog refresh_global_aliases{
"type": "object",
"properties": {}
}Get logging settings · POST /api/v1/get_logging_settings · scope meals:read or GET …?input=
The person's review threshold (meals under it are flagged) and daily nutrient goals (integers; goals_display in kcal and grams). Goals default to the FDA Daily Values for 2,000 kcal until set.
curl -X POST https://getgutlog.com/api/v1/get_logging_settings \ -H "Authorization: Bearer gutlog_…"npx @meterapp/gutlog get_logging_settings{
"type": "object",
"properties": {}
}Update logging settings · POST /api/v1/update_logging_settings · scope meals:write
Set the review threshold (0–1; lower flags fewer meals) and daily goals in display units: {"energy": 1800, "protein": 120, "sodium": 2000} (kcal, grams, mg). null removes a goal.
curl -X POST https://getgutlog.com/api/v1/update_logging_settings \ -H "Authorization: Bearer gutlog_…"npx @meterapp/gutlog update_logging_settings{
"type": "object",
"properties": {
"review_threshold": {
"type": "number",
"minimum": 0,
"maximum": 1
},
"goals": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"anyOf": [
{
"type": "number",
"minimum": 0,
"maximum": 100000
},
{
"type": "null"
}
]
}
}
}
}