Pantry API
18 ops: list_locations, save_location, add_pantry_items, list_pantry, get_pantry_item, update_pantry_items, move_pantry_items, consume_pantry_items, discard_pantry_items, list_expiring, freeze_suggestions, get_raw_ingredients, set_par_levels, get_shopping_list, add_shopping_items, check_off_shopping
list_locations
readList pantry locations · POST /api/v1/list_locations · scope pantry:read or GET …?input=
The household's storage locations as a tree (e.g. Kitchen fridge › Door, Freezer, Garage freezer, Pantry cupboard, Spice rack, Counter), each with its id, path, type (fridge, freezer, pantry, counter, other: the type decides shelf life), how many items are in it and inside it, and how many expire within 7 days. Call it before adding items to reuse the household's own names.
curl -X POST https://getgutlog.com/api/v1/list_locations \ -H "Authorization: Bearer gutlog_…"npx @meterapp/gutlog list_locations{
"type": "object",
"properties": {}
}save_location
writeSave pantry locations · POST /api/v1/save_location · scope pantry:write
Create, rename, retype, nest or archive storage locations. A new one needs a name; its type defaults from the name ("Garage freezer" → freezer), then its parent. Archiving needs the location empty. You rarely need this: add_pantry_items creates the locations it is given by path. Batch: pass up to 200 entries; each succeeds or fails on its own (results[i].ok / results[i].error).
curl -X POST https://getgutlog.com/api/v1/save_location \ -H "Authorization: Bearer gutlog_…" \ -H "Content-Type: application/json" \ -d '{"locations":[{}]}'npx @meterapp/gutlog save_location --json '{"locations":[{}]}'{
"type": "object",
"properties": {
"locations": {
"minItems": 1,
"maxItems": 50,
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"description": "The location to change. Omit to create one.",
"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)$"
},
"name": {
"type": "string",
"minLength": 1,
"maxLength": 80
},
"type": {
"description": "fridge, freezer, pantry, counter or other. Default: from the name, then the parent's type.",
"type": "string",
"enum": [
"fridge",
"freezer",
"pantry",
"counter",
"other"
]
},
"parent_id": {
"description": "The location it is inside, e.g. a shelf in a fridge; null for top level.",
"anyOf": [
{
"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)$"
},
{
"type": "null"
}
]
},
"position": {
"type": "integer",
"minimum": 0,
"maximum": 1000
},
"archived": {
"description": "true archives it (only when empty).",
"type": "boolean"
}
}
}
}
},
"required": [
"locations"
]
}add_pantry_items
writeAdd pantry items · POST /api/v1/add_pantry_items · scope pantry:write
Record food the household has: from a receipt, barcode scans, or a fridge, freezer or pantry photo you read yourself (you list what you see; nothing is analyzed here). Per item: a name (resolved to a canonical ingredient), optional ingredient_id or barcode, quantity and unit, where it is (location path like "Kitchen fridge › Door", location_type, or nothing: the place FoodKeeper keeps it), state (fresh, opened, cooked, frozen, thawed), dates, the printed date and its kind, price and store. Returns each item with its estimated expiry (from the printed date or USDA FoodKeeper for its storage and state, with the rule used), how its shelf life was matched, any storage warning, the locations created, and the names that did not resolve (fix with update_pantry_items). Batch: pass up to 200 entries; each succeeds or fails on its own (results[i].ok / results[i].error).
curl -X POST https://getgutlog.com/api/v1/add_pantry_items \ -H "Authorization: Bearer gutlog_…" \ -H "Content-Type: application/json" \ -d '{"items":[{"name":"scrambled eggs"}]}'npx @meterapp/gutlog add_pantry_items --json '{"items":[{"name":"scrambled eggs"}]}'{
"type": "object",
"properties": {
"items": {
"minItems": 1,
"maxItems": 200,
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "What it is, as the person would say it: \"whole milk\", \"chicken thighs\", \"Rao's marinara\". Resolved to a canonical ingredient when possible."
},
"ingredient_id": {
"description": "Canonical ingredient id, when you know it. Omit to resolve from the name.",
"type": "string",
"minLength": 1,
"maxLength": 100
},
"gtin": {
"description": "Barcode (UPC/EAN/GTIN, 8–14 digits), when you read one.",
"type": "string",
"pattern": "^\\d{8,14}$"
},
"brand": {
"type": "string",
"minLength": 1,
"maxLength": 120
},
"quantity": {
"default": 1,
"description": "How much. Default 1.",
"type": "number",
"exclusiveMinimum": 0,
"maximum": 1000000
},
"unit": {
"description": "Unit: mass (g, kg, oz, lb), volume (ml, l, tsp, tbsp, cup, fl_oz, pt, qt, gal) or a count (each, can, jar, bottle, bag, box, pack, bunch, head, clove, slice, loaf, carton, dozen, stick, tub, pouch). Plurals and \"lbs\", \"gallon\" are understood.",
"type": "string"
},
"location_id": {
"description": "A location id from list_locations.",
"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)$"
},
"location": {
"description": "A location by name or path, e.g. \"Kitchen fridge › Door\", \"Garage freezer\", \"Spice rack\" (› > or / separate levels). Missing levels are created.",
"type": "string",
"minLength": 1,
"maxLength": 200
},
"location_type": {
"description": "fridge, freezer, pantry, counter or other. Alone, it means the first location of that type (created as Fridge, Freezer… if there is none); with `location`, the type of a newly created one.",
"type": "string",
"enum": [
"fridge",
"freezer",
"pantry",
"counter",
"other"
]
},
"state": {
"description": "fresh (sealed or whole, the default), opened, cooked (leftovers), frozen (the default in a freezer), thawed.",
"type": "string",
"enum": [
"fresh",
"opened",
"cooked",
"frozen",
"thawed"
]
},
"purchased_on": {
"description": "Day it was bought (YYYY-MM-DD). Default: today.",
"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])))$"
},
"opened_on": {
"description": "Day it was opened.",
"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])))$"
},
"frozen_on": {
"description": "Day it went into the freezer.",
"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])))$"
},
"thawed_on": {
"description": "Day it came out of the freezer.",
"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])))$"
},
"cooked_on": {
"description": "Day it was cooked (leftovers, meal prep).",
"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])))$"
},
"printed_date": {
"description": "The date printed on the package.",
"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])))$"
},
"printed_kind": {
"description": "What the printed date is: use_by (safety), best_by (quality; the default when a printed date is given), sell_by (for the store), freeze_by.",
"type": "string",
"enum": [
"use_by",
"best_by",
"sell_by",
"freeze_by"
]
},
"expires_on": {
"description": "Set the expiry yourself (overrides the estimate until the item is opened, moved or frozen).",
"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])))$"
},
"foodkeeper_id": {
"description": "Pin the USDA FoodKeeper product its shelf life comes from (get_shelf_life finds one).",
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
},
"price": {
"description": "What it cost, in currency units (3.49).",
"type": "number",
"minimum": 0,
"maximum": 100000
},
"currency": {
"description": "ISO 4217 code. Default USD.",
"type": "string",
"pattern": "^[A-Z]{3}$"
},
"store": {
"description": "Where it was bought.",
"type": "string",
"minLength": 1,
"maxLength": 120
},
"notes": {
"type": "string",
"minLength": 1,
"maxLength": 1000
},
"source": {
"description": "How you captured it: receipt, barcode, photo (you read a fridge or pantry photo), voice, manual. Default manual.",
"type": "string",
"enum": [
"manual",
"receipt",
"barcode",
"photo",
"voice",
"shopping_list",
"meal"
]
},
"client": {
"description": "Which assistant is calling (claude, chatgpt, cursor…), for provenance.",
"type": "string",
"minLength": 1,
"maxLength": 40
}
},
"required": [
"name"
]
},
"description": "Up to 200 items, e.g. a whole receipt or everything you saw in a fridge photo."
}
},
"required": [
"items"
]
}list_pantry
readList pantry items · POST /api/v1/list_pantry · scope pantry:read or GET …?input=
What is on hand, soonest expiry first. Filters: location_id (and everything inside it), location_type, ingredient_id, state, expiring_before (a day), q (name contains), status (active by default; consumed, discarded or all for history). Each item has its ingredient, quantity and unit, location path, state, dates, printed date, expiry (date, latest, days_left, source, rule, safety), freezable, price and provenance. `unresolved` counts items with no canonical ingredient.
curl -X POST https://getgutlog.com/api/v1/list_pantry \ -H "Authorization: Bearer gutlog_…"npx @meterapp/gutlog list_pantry{
"type": "object",
"properties": {
"location_id": {
"description": "Only this location and the locations inside it.",
"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)$"
},
"location_type": {
"type": "string",
"enum": [
"fridge",
"freezer",
"pantry",
"counter",
"other"
]
},
"ingredient_id": {
"type": "string",
"minLength": 1,
"maxLength": 100,
"description": "Canonical ingredient id from the taxonomy (e.g. garlic, natural-flavors)."
},
"state": {
"type": "string",
"enum": [
"fresh",
"opened",
"cooked",
"frozen",
"thawed"
]
},
"expiring_before": {
"description": "Only items whose estimated expiry is on or before this day.",
"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])))$"
},
"q": {
"description": "Name contains.",
"type": "string",
"minLength": 1,
"maxLength": 100
},
"status": {
"default": "active",
"description": "active (on hand, the default), consumed, discarded or all.",
"type": "string",
"enum": [
"active",
"consumed",
"discarded",
"all"
]
},
"limit": {
"default": 200,
"type": "integer",
"minimum": 1,
"maximum": 500
},
"offset": {
"default": 0,
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
}
}get_pantry_item
readGet a pantry item · POST /api/v1/get_pantry_item · scope pantry:read or GET …?input=
One item with its FoodKeeper shelf-life entry (days per storage, opened, thawed, freezer, tips) and its full history (added, used, moved, frozen, thawed, recounted, discarded: who, when, from which surface). Use it to explain an expiry date.
curl -X POST https://getgutlog.com/api/v1/get_pantry_item \ -H "Authorization: Bearer gutlog_…" \ -H "Content-Type: application/json" \ -d '{"item_id":"6f1c2e0a-4b7d-4c2e-9a51-0d3b8e7f9a10"}'npx @meterapp/gutlog get_pantry_item --json '{"item_id":"6f1c2e0a-4b7d-4c2e-9a51-0d3b8e7f9a10"}'{
"type": "object",
"properties": {
"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": "Pantry item id (pantry_items.id)."
}
},
"required": [
"item_id"
]
}update_pantry_items
writeUpdate pantry items · POST /api/v1/update_pantry_items · scope pantry:write
Change items: resolve what it is (ingredient_id, barcode, foodkeeper_id), recount (quantity: how much is there now), change state (opened, cooked; freezing and thawing is move_pantry_items), dates, the printed date, an expiry you set yourself (expires_on; null returns to the estimate), price, store, notes. The expiry is re-estimated when the state or dates change. For food eaten use consume_pantry_items; thrown out, discard_pantry_items. Batch: pass up to 200 entries; each succeeds or fails on its own (results[i].ok / results[i].error).
curl -X POST https://getgutlog.com/api/v1/update_pantry_items \ -H "Authorization: Bearer gutlog_…" \ -H "Content-Type: application/json" \ -d '{"items":[{"item_id":"6f1c2e0a-4b7d-4c2e-9a51-0d3b8e7f9a10"}]}'npx @meterapp/gutlog update_pantry_items --json '{"items":[{"item_id":"6f1c2e0a-4b7d-4c2e-9a51-0d3b8e7f9a10"}]}'{
"type": "object",
"properties": {
"items": {
"minItems": 1,
"maxItems": 200,
"type": "array",
"items": {
"type": "object",
"properties": {
"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": "Pantry item id (pantry_items.id)."
},
"name": {
"type": "string",
"minLength": 1,
"maxLength": 200
},
"ingredient_id": {
"description": "Resolve (or correct) what it is; null clears it.",
"anyOf": [
{
"type": "string",
"minLength": 1,
"maxLength": 100,
"description": "Canonical ingredient id from the taxonomy (e.g. garlic, natural-flavors)."
},
{
"type": "null"
}
]
},
"gtin": {
"anyOf": [
{
"type": "string",
"pattern": "^\\d{8,14}$"
},
{
"type": "null"
}
]
},
"foodkeeper_id": {
"anyOf": [
{
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"brand": {
"anyOf": [
{
"type": "string",
"minLength": 1,
"maxLength": 120
},
{
"type": "null"
}
]
},
"quantity": {
"description": "A recount: how much is there now (an adjust event). Use consume_pantry_items for what was eaten.",
"type": "number",
"minimum": 0,
"maximum": 1000000
},
"unit": {
"description": "Unit: mass (g, kg, oz, lb), volume (ml, l, tsp, tbsp, cup, fl_oz, pt, qt, gal) or a count (each, can, jar, bottle, bag, box, pack, bunch, head, clove, slice, loaf, carton, dozen, stick, tub, pouch). Plurals and \"lbs\", \"gallon\" are understood.",
"type": "string"
},
"state": {
"description": "opened, cooked, thawed… Moving into or out of a freezer is move_pantry_items.",
"type": "string",
"enum": [
"fresh",
"opened",
"cooked",
"frozen",
"thawed"
]
},
"purchased_on": {
"anyOf": [
{
"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])))$"
},
{
"type": "null"
}
]
},
"opened_on": {
"description": "Day it was opened; setting state opened without it means today.",
"anyOf": [
{
"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])))$"
},
{
"type": "null"
}
]
},
"frozen_on": {
"anyOf": [
{
"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])))$"
},
{
"type": "null"
}
]
},
"thawed_on": {
"anyOf": [
{
"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])))$"
},
{
"type": "null"
}
]
},
"cooked_on": {
"anyOf": [
{
"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])))$"
},
{
"type": "null"
}
]
},
"printed_date": {
"description": "The printed date; null clears it.",
"anyOf": [
{
"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])))$"
},
{
"type": "null"
}
]
},
"printed_kind": {
"type": "string",
"enum": [
"use_by",
"best_by",
"sell_by",
"freeze_by"
]
},
"expires_on": {
"description": "Set the expiry yourself; null goes back to the estimate.",
"anyOf": [
{
"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])))$"
},
{
"type": "null"
}
]
},
"price": {
"anyOf": [
{
"type": "number",
"minimum": 0,
"maximum": 100000
},
{
"type": "null"
}
]
},
"currency": {
"type": "string",
"pattern": "^[A-Z]{3}$"
},
"store": {
"anyOf": [
{
"type": "string",
"minLength": 1,
"maxLength": 120
},
{
"type": "null"
}
]
},
"notes": {
"anyOf": [
{
"type": "string",
"minLength": 1,
"maxLength": 1000
},
{
"type": "null"
}
]
}
},
"required": [
"item_id"
]
}
}
},
"required": [
"items"
]
}move_pantry_items
writeMove pantry items · POST /api/v1/move_pantry_items · scope pantry:write
Move items to another location (location_id, a path, or a type). Into a freezer the item becomes frozen from today; out of one, thawed from today; either way the expiry is re-estimated (e.g. chicken: 9 months frozen, then 1–2 days in the fridge after thawing). Batch: pass up to 200 entries; each succeeds or fails on its own (results[i].ok / results[i].error).
curl -X POST https://getgutlog.com/api/v1/move_pantry_items \ -H "Authorization: Bearer gutlog_…" \ -H "Content-Type: application/json" \ -d '{"items":[{"item_id":"6f1c2e0a-4b7d-4c2e-9a51-0d3b8e7f9a10"}]}'npx @meterapp/gutlog move_pantry_items --json '{"items":[{"item_id":"6f1c2e0a-4b7d-4c2e-9a51-0d3b8e7f9a10"}]}'{
"type": "object",
"properties": {
"items": {
"minItems": 1,
"maxItems": 200,
"type": "array",
"items": {
"type": "object",
"properties": {
"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": "Pantry item id (pantry_items.id)."
},
"location_id": {
"description": "A location id from list_locations.",
"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)$"
},
"location": {
"description": "A location by name or path, e.g. \"Kitchen fridge › Door\", \"Garage freezer\", \"Spice rack\" (› > or / separate levels). Missing levels are created.",
"type": "string",
"minLength": 1,
"maxLength": 200
},
"location_type": {
"description": "fridge, freezer, pantry, counter or other. Alone, it means the first location of that type (created as Fridge, Freezer… if there is none); with `location`, the type of a newly created one.",
"type": "string",
"enum": [
"fridge",
"freezer",
"pantry",
"counter",
"other"
]
}
},
"required": [
"item_id"
]
},
"description": "Each item and where it goes. Into a freezer it becomes frozen; out of one, thawed."
}
},
"required": [
"items"
]
}consume_pantry_items
writeUse pantry items · POST /api/v1/consume_pantry_items · scope pantry:write
Record food used: by item_id, or by ingredient_id or name (the soonest-expiring matching items are used first). quantity is optional (all of one item when omitted) and converts between units of the same kind (200 g from a 1 lb pack). Pass meal_id when it went into a logged meal. Items used up leave the pantry and can come back on get_shopping_list. Returns what was used and what is left; `short` when there was not enough. Batch: pass up to 200 entries; each succeeds or fails on its own (results[i].ok / results[i].error).
curl -X POST https://getgutlog.com/api/v1/consume_pantry_items \ -H "Authorization: Bearer gutlog_…" \ -H "Content-Type: application/json" \ -d '{"items":[{}]}'npx @meterapp/gutlog consume_pantry_items --json '{"items":[{}]}'{
"type": "object",
"properties": {
"items": {
"minItems": 1,
"maxItems": 200,
"type": "array",
"items": {
"type": "object",
"properties": {
"item_id": {
"description": "The item used. Or give ingredient_id or name: the soonest-expiring matching items are used first.",
"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)$"
},
"ingredient_id": {
"type": "string",
"minLength": 1,
"maxLength": 100,
"description": "Canonical ingredient id from the taxonomy (e.g. garlic, natural-flavors)."
},
"name": {
"type": "string",
"minLength": 1,
"maxLength": 200
},
"quantity": {
"description": "How much was used. Omit to use it all (by item_id: that item; by ingredient: one item).",
"type": "number",
"exclusiveMinimum": 0,
"maximum": 1000000
},
"unit": {
"description": "Unit of `quantity`; default the item's own. Mass converts to mass, volume to volume.",
"type": "string"
},
"meal_id": {
"description": "The logged meal it went into (log_meal).",
"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": [
"items"
]
}discard_pantry_items
writeThrow out pantry items · POST /api/v1/discard_pantry_items · scope pantry:write
Record food thrown out, all or part, with a reason (expired, spoiled, freezer_burn, didnt_like, too_much, other) for waste tracking; pantry_summary totals it with its cost when the price is known. Batch: pass up to 200 entries; each succeeds or fails on its own (results[i].ok / results[i].error).
curl -X POST https://getgutlog.com/api/v1/discard_pantry_items \ -H "Authorization: Bearer gutlog_…" \ -H "Content-Type: application/json" \ -d '{"items":[{"item_id":"6f1c2e0a-4b7d-4c2e-9a51-0d3b8e7f9a10"}]}'npx @meterapp/gutlog discard_pantry_items --json '{"items":[{"item_id":"6f1c2e0a-4b7d-4c2e-9a51-0d3b8e7f9a10"}]}'{
"type": "object",
"properties": {
"items": {
"minItems": 1,
"maxItems": 200,
"type": "array",
"items": {
"type": "object",
"properties": {
"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": "Pantry item id (pantry_items.id)."
},
"reason": {
"default": "expired",
"description": "expired, spoiled, freezer_burn, didnt_like, too_much or other. Default expired.",
"type": "string",
"enum": [
"expired",
"spoiled",
"freezer_burn",
"didnt_like",
"too_much",
"other"
]
},
"quantity": {
"description": "Only part of it. Omit to throw it all out.",
"type": "number",
"exclusiveMinimum": 0,
"maximum": 1000000
},
"unit": {
"description": "Unit: mass (g, kg, oz, lb), volume (ml, l, tsp, tbsp, cup, fl_oz, pt, qt, gal) or a count (each, can, jar, bottle, bag, box, pack, bunch, head, clove, slice, loaf, carton, dozen, stick, tub, pouch). Plurals and \"lbs\", \"gallon\" are understood.",
"type": "string"
},
"notes": {
"type": "string",
"minLength": 1,
"maxLength": 500
}
},
"required": [
"item_id"
]
}
}
},
"required": [
"items"
]
}list_expiring
readList expiring food · POST /api/v1/list_expiring · scope pantry:read or GET …?input=
Items expiring within `days` (default 7) and, by default, already past their date, soonest first, each with days_left and a suggested action: use, freeze (freezable, not frozen, 2 days or less left) or toss (past a use-by or the safe time for perishable food), with a one-line why. Past a best-by or a freezer quality date the action is use (check it), not toss. Use it to suggest what to cook first.
curl -X POST https://getgutlog.com/api/v1/list_expiring \ -H "Authorization: Bearer gutlog_…"npx @meterapp/gutlog list_expiring{
"type": "object",
"properties": {
"days": {
"default": 7,
"description": "Look this many days ahead. Default 7.",
"type": "integer",
"minimum": 0,
"maximum": 365
},
"include_expired": {
"default": true,
"description": "Also list items already past their date. Default true.",
"type": "boolean"
},
"location_type": {
"type": "string",
"enum": [
"fridge",
"freezer",
"pantry",
"counter",
"other"
]
}
}
}Suggest what to freeze · POST /api/v1/freeze_suggestions · scope pantry:read or GET …?input=
Items not frozen that FoodKeeper says freeze well and that expire within `days` (default 3), with how long each would keep if frozen today. Never items already past their date, nor thawed food that did not thaw in the fridge. Freeze them with move_pantry_items to a freezer.
curl -X POST https://getgutlog.com/api/v1/freeze_suggestions \ -H "Authorization: Bearer gutlog_…"npx @meterapp/gutlog freeze_suggestions{
"type": "object",
"properties": {
"days": {
"default": 3,
"description": "Items expiring within this many days. Default 3.",
"type": "integer",
"minimum": 0,
"maximum": 60
}
}
}Get raw ingredients on hand · POST /api/v1/get_raw_ingredients · scope pantry:read or GET …?input=
The canonical ingredients on hand, deduplicated across items and locations: total amount (mass in g/kg, volume in ml/l, counts per unit), item ids, states, locations, soonest expiry and days left, soonest first; plus `unresolved` items with no ingredient. This is the list to plan meals or recipes from.
curl -X POST https://getgutlog.com/api/v1/get_raw_ingredients \ -H "Authorization: Bearer gutlog_…"npx @meterapp/gutlog get_raw_ingredients{
"type": "object",
"properties": {}
}set_par_levels
writeSet staple quantities · POST /api/v1/set_par_levels · scope pantry:write
Staples the household always wants on hand ("12 eggs", "1 gal milk", "2 cans tomatoes"), with an optional usual location and store. quantity 0 removes a staple. get_shopping_list adds staples below their level. Batch: pass up to 200 entries; each succeeds or fails on its own (results[i].ok / results[i].error).
curl -X POST https://getgutlog.com/api/v1/set_par_levels \ -H "Authorization: Bearer gutlog_…" \ -H "Content-Type: application/json" \ -d '{"levels":[{"name":"scrambled eggs","quantity":0}]}'npx @meterapp/gutlog set_par_levels --json '{"levels":[{"name":"scrambled eggs","quantity":0}]}'{
"type": "object",
"properties": {
"levels": {
"minItems": 1,
"maxItems": 200,
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "The staple: \"eggs\", \"milk\", \"canned tomatoes\"."
},
"ingredient_id": {
"type": "string",
"minLength": 1,
"maxLength": 100,
"description": "Canonical ingredient id from the taxonomy (e.g. garlic, natural-flavors)."
},
"quantity": {
"type": "number",
"minimum": 0,
"maximum": 1000000,
"description": "How much to always have. 0 removes the staple."
},
"unit": {
"description": "Unit: mass (g, kg, oz, lb), volume (ml, l, tsp, tbsp, cup, fl_oz, pt, qt, gal) or a count (each, can, jar, bottle, bag, box, pack, bunch, head, clove, slice, loaf, carton, dozen, stick, tub, pouch). Plurals and \"lbs\", \"gallon\" are understood.",
"type": "string"
},
"location_id": {
"description": "A location id from list_locations.",
"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)$"
},
"location": {
"description": "A location by name or path, e.g. \"Kitchen fridge › Door\", \"Garage freezer\", \"Spice rack\" (› > or / separate levels). Missing levels are created.",
"type": "string",
"minLength": 1,
"maxLength": 200
},
"location_type": {
"description": "fridge, freezer, pantry, counter or other. Alone, it means the first location of that type (created as Fridge, Freezer… if there is none); with `location`, the type of a newly created one.",
"type": "string",
"enum": [
"fridge",
"freezer",
"pantry",
"counter",
"other"
]
},
"store": {
"type": "string",
"minLength": 1,
"maxLength": 120
}
},
"required": [
"name",
"quantity"
]
}
}
},
"required": [
"levels"
]
}Get the shopping list · POST /api/v1/get_shopping_list · scope pantry:read or GET …?input=
What to buy: items added to the list, staples below their par level (with the amount missing), and items used up in the last `used_up_days` (default 14) that are not on hand. Each line has a key (check it off by key), source (manual, par, used_up) and why; `by_store` groups them by store when known.
curl -X POST https://getgutlog.com/api/v1/get_shopping_list \ -H "Authorization: Bearer gutlog_…"npx @meterapp/gutlog get_shopping_list{
"type": "object",
"properties": {
"used_up_days": {
"default": 14,
"description": "Suggest items used up in this many days that are not on hand. Default 14; 0 turns it off.",
"type": "integer",
"minimum": 0,
"maximum": 90
}
}
}add_shopping_items
writeAdd to the shopping list · POST /api/v1/add_shopping_items · scope pantry:write
Put items on the shopping list (name, optional ingredient_id, quantity and unit, store, notes). Adding something already on the list adds to its quantity. Batch: pass up to 200 entries; each succeeds or fails on its own (results[i].ok / results[i].error).
curl -X POST https://getgutlog.com/api/v1/add_shopping_items \ -H "Authorization: Bearer gutlog_…" \ -H "Content-Type: application/json" \ -d '{"items":[{"name":"scrambled eggs"}]}'npx @meterapp/gutlog add_shopping_items --json '{"items":[{"name":"scrambled eggs"}]}'{
"type": "object",
"properties": {
"items": {
"minItems": 1,
"maxItems": 200,
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
"maxLength": 200
},
"ingredient_id": {
"type": "string",
"minLength": 1,
"maxLength": 100,
"description": "Canonical ingredient id from the taxonomy (e.g. garlic, natural-flavors)."
},
"quantity": {
"type": "number",
"exclusiveMinimum": 0,
"maximum": 1000000
},
"unit": {
"description": "Unit: mass (g, kg, oz, lb), volume (ml, l, tsp, tbsp, cup, fl_oz, pt, qt, gal) or a count (each, can, jar, bottle, bag, box, pack, bunch, head, clove, slice, loaf, carton, dozen, stick, tub, pouch). Plurals and \"lbs\", \"gallon\" are understood.",
"type": "string"
},
"store": {
"type": "string",
"minLength": 1,
"maxLength": 120
},
"notes": {
"type": "string",
"minLength": 1,
"maxLength": 500
}
},
"required": [
"name"
]
}
}
},
"required": [
"items"
]
}Check off shopping items · POST /api/v1/check_off_shopping_items · scope pantry:write
Mark list lines bought (or removed). By id, or key/name for a suggested line. Bought items go into the pantry (add_to_pantry, default true) at the staple's usual location or where FoodKeeper keeps them, with today's purchase date and any printed date, price and store you give. Batch: pass up to 200 entries; each succeeds or fails on its own (results[i].ok / results[i].error).
curl -X POST https://getgutlog.com/api/v1/check_off_shopping_items \ -H "Authorization: Bearer gutlog_…" \ -H "Content-Type: application/json" \ -d '{"items":[{}]}'npx @meterapp/gutlog check_off_shopping_items --json '{"items":[{}]}'{
"type": "object",
"properties": {
"items": {
"minItems": 1,
"maxItems": 200,
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"description": "A shopping list item id. Suggested rows have none: give their key or name.",
"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)$"
},
"key": {
"type": "string",
"minLength": 1,
"maxLength": 200
},
"name": {
"type": "string",
"minLength": 1,
"maxLength": 200
},
"status": {
"default": "bought",
"type": "string",
"enum": [
"bought",
"removed"
]
},
"add_to_pantry": {
"default": true,
"description": "Put a bought item into the pantry. Default true.",
"type": "boolean"
},
"quantity": {
"description": "How much was bought; default the list's quantity, or 1.",
"type": "number",
"exclusiveMinimum": 0,
"maximum": 1000000
},
"unit": {
"description": "Unit: mass (g, kg, oz, lb), volume (ml, l, tsp, tbsp, cup, fl_oz, pt, qt, gal) or a count (each, can, jar, bottle, bag, box, pack, bunch, head, clove, slice, loaf, carton, dozen, stick, tub, pouch). Plurals and \"lbs\", \"gallon\" are understood.",
"type": "string"
},
"location_id": {
"description": "A location id from list_locations.",
"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)$"
},
"location": {
"description": "A location by name or path, e.g. \"Kitchen fridge › Door\", \"Garage freezer\", \"Spice rack\" (› > or / separate levels). Missing levels are created.",
"type": "string",
"minLength": 1,
"maxLength": 200
},
"location_type": {
"description": "fridge, freezer, pantry, counter or other. Alone, it means the first location of that type (created as Fridge, Freezer… if there is none); with `location`, the type of a newly created one.",
"type": "string",
"enum": [
"fridge",
"freezer",
"pantry",
"counter",
"other"
]
},
"printed_date": {
"description": "The date printed on the package.",
"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])))$"
},
"printed_kind": {
"description": "What the printed date is: use_by (safety), best_by (quality; the default when a printed date is given), sell_by (for the store), freeze_by.",
"type": "string",
"enum": [
"use_by",
"best_by",
"sell_by",
"freeze_by"
]
},
"price": {
"type": "number",
"minimum": 0,
"maximum": 100000
},
"currency": {
"type": "string",
"pattern": "^[A-Z]{3}$"
},
"store": {
"type": "string",
"minLength": 1,
"maxLength": 120
}
}
}
}
},
"required": [
"items"
]
}pantry_summary
readPantry summary · POST /api/v1/pantry_summary · scope pantry:read or GET …?input=
Counts on hand by location and state, how many are expired and expiring this week, unresolved items, and this month's use and waste: items thrown out, their cost when known, reasons and the foods most often thrown out.
curl -X POST https://getgutlog.com/api/v1/pantry_summary \ -H "Authorization: Bearer gutlog_…"npx @meterapp/gutlog pantry_summary{
"type": "object",
"properties": {}
}Look up shelf life · POST /api/v1/get_shelf_life · scope pantry:read or GET …?input=
How long a food keeps in the pantry, fridge and freezer, sealed, opened and after thawing, from USDA FoodKeeper (public domain), by name, ingredient_id or foodkeeper_id. Days as {min, max} or a marker: package_date, indefinitely, when_ripe, not_recommended. Use the returned foodkeeper_id to pin an item's shelf life. Needs no account.
curl -X POST https://getgutlog.com/api/v1/get_shelf_life \ -H "Authorization: Bearer gutlog_…"npx @meterapp/gutlog get_shelf_life{
"type": "object",
"properties": {
"name": {
"description": "A food as people say it: \"ketchup\", \"ground beef\".",
"type": "string",
"minLength": 1,
"maxLength": 200
},
"ingredient_id": {
"type": "string",
"minLength": 1,
"maxLength": 100,
"description": "Canonical ingredient id from the taxonomy (e.g. garlic, natural-flavors)."
},
"foodkeeper_id": {
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
}
}
}