Products API
The barcode index: look up, scan labels and submit products. 6 ops: lookup_barcode, get_product, scan_label, submit_product, list_submissions, review_submission.
The barcode index: look up, scan labels and submit products.
lookup_barcode
readLook up a barcode · POST /api/v1/lookup_barcode · scope products:read or GET …?input=
Find a packaged food by its barcode: our product index first (instant), then Open Food Facts, then USDA Branded Foods; a product found outside the index is added to it. Returns found, the product (brand, name, serving, nutrients per serving and per 100 g as integers plus display_per_serving in kcal/g, ingredient text, parsed ingredient ids, allergens, status unverified | community_verified | verified, where each field came from and any attribution to show, e.g. Open Food Facts under ODbL) and found_in (index, off, usda_branded). When found is false, ask the person for a photo of the nutrition label and ingredients, then call submit_product with what you read (free) or scan_label with the photos. Free and unlimited. To log the product as eaten, pass the barcode to log_meal instead.
curl -X POST https://getgutlog.com/api/v1/lookup_barcode \ -H "Authorization: Bearer gutlog_…" \ -H "Content-Type: application/json" \ -d '{"code":"code"}'npx @meterapp/gutlog lookup_barcode --json '{"code":"code"}'{
"type": "object",
"properties": {
"code": {
"type": "string",
"minLength": 8,
"maxLength": 20,
"description": "The barcode digits: UPC-A (12), EAN-13, EAN-8 or GTIN-14. Spaces and dashes are ignored."
}
},
"required": [
"code"
]
}Get a product · POST /api/v1/get_product · scope products:read or GET …?input=
A product already in our index by GTIN or id: brand, name, serving, nutrients per serving and per 100 g, ingredient text and parsed ingredient ids, allergens, verification status, the source of every field and the attribution to show, and its public page URL. Never calls outside services (use lookup_barcode for a code we may not have yet). Free; no sign-in needed.
curl -X POST https://getgutlog.com/api/v1/get_product \ -H "Authorization: Bearer gutlog_…"npx @meterapp/gutlog get_product{
"type": "object",
"properties": {
"gtin": {
"type": "string",
"minLength": 8,
"maxLength": 20,
"description": "The barcode digits: UPC-A (12), EAN-13, EAN-8 or GTIN-14. Spaces and dashes are ignored."
},
"id": {
"description": "Product id (products.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)$"
}
}
}scan_label
writeRead a label photo · POST /api/v1/scan_label · scope products:write
Read a food package from photos on our model: the nutrition facts panel, serving size, ingredient statement and the barcode digits if visible. Returns the label (nutrients as integers in stored units: energy in millikilocalories, mass in milligrams), per 100 g, a display version, parsed ingredient ids with allergens and what did not match. With gtin (or a readable barcode) and submit true (default), it also submits the label to the product index, like submit_product. Uses one analysis from the daily allowance (402 plan_limit past it). If you can see the photo yourself (ChatGPT, Claude), read it and call submit_product instead: that is free.
curl -X POST https://getgutlog.com/api/v1/scan_label \ -H "Authorization: Bearer gutlog_…" \ -H "Content-Type: application/json" \ -d '{"images":[{"media_type":"image/jpeg","data":"data"}]}'npx @meterapp/gutlog scan_label --json '{"images":[{"media_type":"image/jpeg","data":"data"}]}'{
"type": "object",
"properties": {
"images": {
"minItems": 1,
"maxItems": 4,
"type": "array",
"items": {
"type": "object",
"properties": {
"media_type": {
"type": "string",
"enum": [
"image/jpeg",
"image/png",
"image/webp",
"image/gif"
]
},
"data": {
"type": "string",
"minLength": 100,
"maxLength": 7000000,
"description": "The photo, base64-encoded (no data: prefix), at most ~5 MB."
}
},
"required": [
"media_type",
"data"
]
},
"description": "1–4 photos: the nutrition panel and the ingredient list (and the barcode)."
},
"gtin": {
"description": "The product's barcode, if known.",
"type": "string",
"minLength": 8,
"maxLength": 20
},
"submit": {
"default": true,
"description": "Submit the reading to the product index when a GTIN is known (default true).",
"type": "boolean"
}
},
"required": [
"images"
]
}submit_product
writeSubmit a product label · POST /api/v1/submit_product · scope products:write
Add or correct a packaged food in the product index from its label: the barcode plus what the label says (brand, name, serving size and grams, the nutrition panel per serving as integers in stored units, the ingredient statement verbatim). A new barcode creates the product (unverified); a label matching ours confirms it, and a second person's matching label verifies it (community_verified); a different label is kept as a pending revision with a diff until another person's label or staff review confirms it. Returns submission_id, status (pending, matched, conflicting, accepted), the product, the diff and a message to relay. Free; uses no analysis.
curl -X POST https://getgutlog.com/api/v1/submit_product \ -H "Authorization: Bearer gutlog_…" \ -H "Content-Type: application/json" \ -d '{"gtin":"gtin","label":{"name":"scrambled eggs"}}'npx @meterapp/gutlog submit_product --json '{"gtin":"gtin","label":{"name":"scrambled eggs"}}'{
"type": "object",
"properties": {
"gtin": {
"type": "string",
"minLength": 8,
"maxLength": 20,
"description": "The barcode digits: UPC-A (12), EAN-13, EAN-8 or GTIN-14. Spaces and dashes are ignored."
},
"label": {
"type": "object",
"properties": {
"brand": {
"description": "Brand as printed, e.g. \"Kind\".",
"type": "string",
"minLength": 1,
"maxLength": 120
},
"name": {
"type": "string",
"minLength": 1,
"maxLength": 300,
"description": "Product name as printed, without the brand, e.g. \"Dark Chocolate Nuts & Sea Salt bar\"."
},
"serving_label": {
"description": "Serving size as printed, e.g. \"1 bar (40g)\" or \"2/3 cup (55g)\".",
"type": "string",
"minLength": 1,
"maxLength": 120
},
"serving_grams": {
"description": "Grams (or millilitres for drinks) in one serving.",
"type": "number",
"exclusiveMinimum": 0,
"maximum": 5000
},
"nutrients_per_serving": {
"description": "The nutrition panel per serving.",
"type": "object",
"properties": {
"energy": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"protein": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"carbs": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"fat": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"saturated_fat": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"trans_fat": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"fiber": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"sugar": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"added_sugar": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"sodium": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"cholesterol": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"potassium": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"calcium": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"iron": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"magnesium": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"zinc": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"vitamin_c": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"vitamin_a": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"vitamin_d": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"vitamin_b12": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"folate": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"caffeine": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
}
},
"additionalProperties": false
},
"nutrients_per_100g": {
"description": "Per 100 g, when the label prints it (EU labels); otherwise derived from per serving and serving_grams.",
"type": "object",
"properties": {
"energy": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"protein": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"carbs": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"fat": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"saturated_fat": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"trans_fat": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"fiber": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"sugar": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"added_sugar": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"sodium": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"cholesterol": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"potassium": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"calcium": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"iron": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"magnesium": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"zinc": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"vitamin_c": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"vitamin_a": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"vitamin_d": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"vitamin_b12": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"folate": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
},
"caffeine": {
"type": "integer",
"minimum": 0,
"maximum": 1000000000
}
},
"additionalProperties": false
},
"ingredients_text": {
"description": "The ingredient statement exactly as printed, including any \"Contains:\" line.",
"type": "string",
"minLength": 1,
"maxLength": 5000
}
},
"required": [
"name"
]
}
},
"required": [
"gtin",
"label"
]
}list_submissions
readList product submissions · POST /api/v1/list_submissions · scope products:read or GET …?input=
Gutlog staff: product label submissions waiting for review (pending and conflicting by default, oldest first) with the label, the diff against the product and the revision it proposes.
curl -X POST https://getgutlog.com/api/v1/list_submissions \ -H "Authorization: Bearer gutlog_…"npx @meterapp/gutlog list_submissions{
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": [
"pending",
"matched",
"conflicting",
"accepted",
"rejected"
]
},
"limit": {
"default": 50,
"type": "integer",
"minimum": 1,
"maximum": 200
}
}
}review_submission
writeReview a product submission · POST /api/v1/review_submission · scope products:write
Gutlog staff: accept a label submission (its label becomes the product's, recorded as a revision with a diff, and the product is verified) or reject it (a pending revision it proposed is dropped).
curl -X POST https://getgutlog.com/api/v1/review_submission \ -H "Authorization: Bearer gutlog_…" \ -H "Content-Type: application/json" \ -d '{"submission_id":"6f1c2e0a-4b7d-4c2e-9a51-0d3b8e7f9a10","decision":"accept"}'npx @meterapp/gutlog review_submission --json '{"submission_id":"6f1c2e0a-4b7d-4c2e-9a51-0d3b8e7f9a10","decision":"accept"}'{
"type": "object",
"properties": {
"submission_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)$"
},
"decision": {
"type": "string",
"enum": [
"accept",
"reject"
]
},
"note": {
"description": "Why, for the record.",
"type": "string",
"maxLength": 2000
}
},
"required": [
"submission_id",
"decision"
]
}