# Gutlog > Gutlog logs what you eat from a photo in ChatGPT or Claude: calories, macros, nutrients and ingredients with a confidence score you can fix in one tap. Clean-ingredient verdicts, organic advice, a barcode index, tummy scores, trigger analysis and elimination diets, from the web app, MCP, the CLI or the API. ## Setup A person sets Gutlog up by pasting one sentence into their assistant: "Set up Gutlog for me: read https://getgutlog.com/setup.md and follow it." - [setup.md](https://getgutlog.com/setup.md): instructions for the assistant: which client it runs in, the command or the exact clicks, then verify with `get_onboarding` and log a first meal. - [Setup page](https://getgutlog.com/setup?ref=llms): every client with copy buttons and one-click links (ChatGPT, Claude, Claude Code, Codex, Cursor, VS Code, Gemini CLI, other MCP clients). ## Connect and authenticate - MCP (streamable HTTP): `https://getgutlog.com/api/mcp`. One tool per op. OAuth sign-in through the browser (see https://getgutlog.com/.well-known/oauth-protected-resource), or an API key. Setup for every client: https://getgutlog.com/setup.md. - REST: `POST https://getgutlog.com/api/v1/{op}` with the op's input as JSON; read-only ops also accept GET with `?input=`. `GET https://getgutlog.com/api/v1` lists every op with its JSON Schema. - CLI: `npx @meterapp/gutlog [--field value] [--json '']`; `npx @meterapp/gutlog login` signs in with the browser and saves a key. In scripts set `GUTLOG_API_KEY`. - TypeScript SDK: `npm install @meterapp/gutlog-sdk`, then `new Gutlog().call("log_meal", {...})`: typed methods per op, retries with idempotency keys. Guide: https://getgutlog.com/docs/sdk?ref=llms. - API keys: `Authorization: Bearer gutlog_…`. Create one in the web app, with `npx @meterapp/gutlog login` or `create_api_key`. A key can be narrowed to scopes: `:read`, `:write`, `:*`, `*:read`, and `billing:purchase` (implied by nothing). - Errors are RFC 9457 problem+json with a `code` and `request_id`; a 402 carries a checkout_url. See https://getgutlog.com/docs/errors?ref=llms. Claude Code: ```bash claude mcp add --transport http gutlog https://getgutlog.com/api/mcp ``` ## Workflows 1. Orient: `get_onboarding` (the setup checklist and the next step), `get_account` (plan and today's analyses). 2. Log: the person shares a photo or describes a meal → `log_meal` with your draft → one line back plus the returned questions. A barcode you can read goes in the item's `barcode`. 3. Review: `get_day` (a day's meals, totals and how many need review). Edit ops (portions, confirming or rejecting ingredients, nutrient overrides) are listed under Meals as they ship. 4. Gut, clean and programs: symptom logging, triggers, clean verdicts and elimination programs are listed below as they ship; always run a program's safety screen first. 5. Billing: `get_pricing` → `create_checkout_link` for a human to pay; `get_billing_link` for invoices and cancellation. ## Rules - Start with `get_onboarding`: it says what is set up, what is next and which op does it. - Log first, fix later: when the person shares a meal photo or describes a meal, call `log_meal` with your own draft right away (it is free and unlimited), reply with one line of what was logged, then ask the `questions` it returns (at most three). Ingredient confidence: ≥0.85 seen or told; 0.6–0.85 very likely; 0.3–0.6 plausible but hidden (cooking fat, butter, sugar, garlic, onion); leave out anything below 0.3. - Gutlog is not medical advice and never diagnoses. Describe gut patterns as hypotheses to test, with how much data backs them. Elimination programs start with a safety screen and recommend a registered dietitian; never help someone skip the screen. Send red-flag symptoms (blood in stool, unintended weight loss, fever, night-time symptoms) to a doctor. - Food, symptom and program data are health data. Do not copy them anywhere outside the conversation and the tools. - Clean-ingredient verdicts come from our own published rubric with an evidence level per flag; say when a flag is a preference rather than settled science. - Never buy, change or cancel a plan on your own initiative. Quote the plan and price from get_pricing, and only call subscribe or cancel_subscription with confirm: true after a human approved it; otherwise give them the create_checkout_link URL. - Use read-only ops freely; every write is recorded in the activity log with your surface. - When a call fails in a way that looks like a Gutlog bug, report_issue with the error's request_id, the op and context; when the human wishes Gutlog did something it cannot, request_feature. If they come back filed: false with `similar`, upvote_feedback the match instead. Never invent a review: submit_review only with the human's rating and words. ## Meals, confidence and units The host model does the vision: it looks at the photo and sends `log_meal` a draft (items with grams or a household portion, an optional barcode, cooking method, and ingredients with a confidence each). Gutlog matches each item against the person's own foods, the barcode index and the food database, then portions, scores and stores it. The meal is always stored; `needs_review` is set when the meal confidence is under the person's threshold (default 0.80) or a possible ingredient is a known trigger. Ingredient tiers: confirmed ≥0.85 (or from a label, counted), likely 0.6–0.85 (counted), possible 0.3–0.6 (suggested, not counted), dropped below 0.3. Guide: https://getgutlog.com/docs/meal-drafts?ref=llms. Nutrients are integers in a base unit, never floats: energy (mkcal), protein (mg), carbs (mg), fat (mg), saturated_fat (mg), trans_fat (mg), fiber (mg), sugar (mg), added_sugar (mg), sodium (mg), cholesterol (mg), potassium (mg), calcium (mg), iron (mg), magnesium (mg), zinc (mg), vitamin_c (mg), vitamin_a (ug), vitamin_d (ug), vitamin_b12 (ug), folate (ug), caffeine (mg). mkcal is millikilocalories (1 kcal = 1000 mkcal); ug is micrograms. `totals_display` carries kcal and grams for people. Guide: https://getgutlog.com/docs/nutrients?ref=llms. Example draft: ```json { "title": "Scrambled eggs on toast", "meal_type": "breakfast", "client": "claude", "items": [ { "name": "scrambled eggs", "grams": 120, "confidence": 0.92, "portion_confidence": 0.7, "cooking_method": "pan_fried", "ingredients": [ { "name": "egg", "confidence": 0.97, "visible": true }, { "name": "butter", "confidence": 0.55, "visible": false }, { "name": "milk", "confidence": 0.35, "visible": false } ] }, { "name": "sourdough toast", "portion": { "quantity": 1, "unit": "slice" }, "confidence": 0.85, "ingredients": [ { "name": "wheat flour", "confidence": 0.9, "visible": false }, { "name": "salt", "confidence": 0.75, "visible": false } ] } ] } ``` ## Pricing Unit: analyses: one photo or text analysis on our model (a photo sent through the web app, CLI, API or email; free text; a label photo), counted per UTC day. Logging a meal your assistant already analyzed (ChatGPT, Claude over MCP), barcode scans of indexed products and edits are free and unlimited. Yearly is $60 instead of $96. The full pricing document (JSON, no credential, the same shape on every Meter product): `GET https://getgutlog.com/api/v1/pricing`; MCP `get_pricing` (also on the no-auth catalog server https://getgutlog.com/api/mcp/catalog); CLI `npx @meterapp/gutlog pricing --json`; human page: https://getgutlog.com/pricing?ref=agent. - Free (`free`): free. 0 photo or text analyses a day on our model, unlimited history; logging from your assistant is unlimited. Your food database, from your own AI assistant. - Plus (`plus`): $8/month or $60/year. 50 photo or text analyses a day on our model, unlimited history; logging from your assistant is unlimited. Gutlog does the work: the app, reminders, plans and gut analysis. Buy: https://getgutlog.com/checkout?plan=plus&ref=agent Limits: No overage: analyses past the 0 a day are refused until the next UTC day or an upgrade. Logging a meal the assistant already analyzed, barcode scans and edits never count. Buying a plan (never collect card details; the human pays on Stripe): - MCP `create_checkout_link`, CLI `gutlog upgrade --plan plus [--yearly]`, REST `POST /api/v1/billing/checkout` return a Stripe Checkout URL for a human. Nothing is charged until they complete it. - Before signup: create_checkout_link needs no account: without a credential it returns a Stripe Checkout URL for a human. After paying they sign in to Gutlog with the same email; the subscription attaches to their organization on return (or claim_checkout {session_id}). - With a saved card: subscribe {plan, interval, confirm: true} changes the plan on the organization's saved card, only after a human approved the price. - Account and invoices: get_billing_link (invoices, card, cancel); get_account (plan, usage, what the next plan adds). ## Events Things that happened in an account, in `list_events` and the activity log (ids and counts only, never food or symptom text): `meal.logged`, `meal.updated`, `meal.reviewed`, `meal.deleted`, `product.submitted`, `product.verified`, `symptoms.logged`, `program.started`, `program.phase_changed`, `program.completed`, `analysis.rated`. ## Feedback and reviews Feature requests are public on https://getgutlog.com/feedback?ref=llms; issue reports are private to the reporter and the Gutlog team. Reviews are public on https://getgutlog.com/reviews?ref=llms, one per account. Guide: https://getgutlog.com/docs/feedback?ref=llms. | action | MCP tool | CLI | REST | | --- | --- | --- | --- | | list / search the board | `list_feedback` | `npx @meterapp/gutlog feedback list` | `GET https://getgutlog.com/api/v1/feedback` (public) | | request a feature | `request_feature` | `npx @meterapp/gutlog feedback request ""` | `POST https://getgutlog.com/api/v1/feedback/features` | | report an issue | `report_issue` | `npx @meterapp/gutlog feedback report "<title>" [--last]` | `POST https://getgutlog.com/api/v1/feedback/issues` | | upvote | `upvote_feedback` | `npx @meterapp/gutlog feedback upvote <id>` | `POST https://getgutlog.com/api/v1/feedback/{id}/votes` | | post / edit a review | `submit_review` | `npx @meterapp/gutlog review --rating 5 --body "…"` | `PUT https://getgutlog.com/api/v1/reviews/me` | ## Client configs ### Claude Code ```bash claude mcp add --transport http gutlog https://getgutlog.com/api/mcp ``` ### Codex ```bash codex mcp add gutlog --url https://getgutlog.com/api/mcp codex mcp login gutlog ``` ### Cursor (~/.cursor/mcp.json) ```json { "mcpServers": { "gutlog": { "url": "https://getgutlog.com/api/mcp" } } } ``` ### stdio-only MCP hosts ```json { "mcpServers": { "gutlog": { "command": "npx", "args": [ "-y", "@meterapp/gutlog", "mcp" ], "env": { "GUTLOG_API_KEY": "gutlog_…" } } } } ``` ### CLI ```bash npx @meterapp/gutlog login npx @meterapp/gutlog log_meal --json '{"title":"Scrambled eggs on toast","meal_type":"breakfast","client":"claude","items":[{"name":"scrambled eggs","grams":120,"confidence":0.92,"portion_confidence":0.7,"cooking_method":"pan_fried","ingredients":[{"name":"egg","confidence":0.97,"visible":true},{"name":"butter","confidence":0.55,"visible":false},{"name":"milk","confidence":0.35,"visible":false}]},{"name":"sourdough toast","portion":{"quantity":1,"unit":"slice"},"confidence":0.85,"ingredients":[{"name":"wheat flour","confidence":0.9,"visible":false},{"name":"salt","confidence":0.75,"visible":false}]}]}' npx @meterapp/gutlog get_day ``` ### TypeScript SDK ```bash npm install @meterapp/gutlog-sdk ``` ```ts import { Gutlog } from "@meterapp/gutlog-sdk"; const gutlog = new Gutlog(); // reads GUTLOG_API_KEY const meal = await gutlog.call("log_meal", { items: [ { name: "greek yogurt", grams: 170, ingredients: [{ name: "milk", confidence: 0.95 }] }, { name: "blueberries", portion: { quantity: 0.5, unit: "cup" } }, ], }); const day = await gutlog.call("get_day", {}); console.log(meal, day); ``` ## Errors Every failure is `application/problem+json` (RFC 9457): `type`, `title` (the code), `status`, `detail`, `code`, `request_id`. - 400 `invalid_json`: The body (or ?input for GET) is not valid JSON. - 400 `invalid_input`: The input failed the op's schema. `issues` lists each problem with its path. - 400 `invalid_slug`: The name produced no usable slug. Use letters, digits and dashes. - 400 `confirm_mismatch`: A destructive op needs its target's name typed back in `confirm`, and it did not match. - 400 `principal_required`: Name exactly one team or person to share with or remove. - 400 `same_person`: The op cannot target yourself (for example transferring ownership to yourself). - 400 `no_person`: No member of this organization has that email address or id. - 400 `invalid_grant`: CLI sign-in: the device code is unknown or was already used. Run login again. - 400 `expired_token`: CLI sign-in: the device code expired before it was approved. Run login again. - 400 `access_denied`: CLI sign-in: the person declined the request in the browser. - 401 `unauthorized`: No credential. Send `Authorization: Bearer gutlog_…`. - 401 `invalid_key`: The API key does not exist or was revoked. - 401 `expired_key`: The API key passed its expiry date. Create a new one. - 401 `invalid_token`: The OAuth access token is invalid or expired. Refresh it and retry. - 402 `payment_failed`: Stripe declined the charge for a plan change. Ask a human to update the card (get_billing_link) or pay through create_checkout_link. - 402 `plan_limit`: The plan's daily allowance (photo and text analyses) or a plan-gated feature is used up or not included. The problem carries `checkout_url`; logging a draft from your assistant never hits it. - 403 `forbidden`: The op needs a higher role than this credential has. `required_role` names it. - 403 `scope_missing`: The API key's scopes do not cover this op. `required_scope` names the scope it needs. - 403 `scope_escalation`: A scoped key tried to create a key with wider scopes than its own. - 403 `dashboard_only`: A person does this in the dashboard (inviting members, changing roles); it is not available to keys. - 403 `person_required`: Only a signed-in person can do this, not an API key. - 403 `no_organization`: The signed-in user is not a member of the requested organization (or of any). - 404 `unknown_op`: No op has that name. `GET /api/v1` lists them. - 404 `not_found`: The meal, food, product or other record is not in this account, or you cannot see it. - 405 `method_not_allowed`: A write op was sent with GET. Use POST. - 409 `organization_exists`: An organization with that slug already exists. - 409 `team_exists`: A team with that name already exists in this organization. - 409 `already_member`: That person is already a member of the organization. - 409 `last_owner`: An organization keeps at least one owner: the last owner cannot leave or be demoted. - 409 `already_subscribed`: The organization is already on that plan and interval. get_billing shows the subscription. - 409 `subscription_active`: The organization already pays for a plan; change it with subscribe or the billing portal. - 409 `no_subscription`: There is no paid subscription to cancel or resume. - 409 `checkout_incomplete`: The Stripe Checkout session was not paid yet. - 409 `already_claimed`: That Checkout session is already attached to an organization. - 409 `checkout_unavailable`: The Checkout session cannot be used any more (for example its customer was deleted). Start a new one. - 409 `no_email`: The credential has no email address behind it, so there is nothing to subscribe. Sign in to the web app instead. - 413 `context_too_large`: The context attached to an issue report is over 128 KB. - 428 `authorization_pending`: CLI sign-in: the person has not approved the code yet. Poll again after `interval` seconds. - 429 `rate_limited`: Too many requests. Wait for `Retry-After` seconds and retry. - 500 `internal_error`: Something failed on our side. Retry, and quote `request_id` if it persists. - 503 `billing_unavailable`: Billing is not configured on this server yet. Email support to subscribe. ## Ops reference Call any op with `POST https://getgutlog.com/api/v1/{name}` and its input as JSON. Read-only ops also accept GET with `?input=<url-encoded JSON>`. ### get_organization Get organization. Read-only. Group: organization. Scope: organization:read. The organization this credential acts in (a person's own diary is their personal organization): id, slug, name, timezone (the diary day boundary) and language, and the caller's role. Call first to orient yourself; get_account adds the plan and usage. Input schema: ```json { "type": "object", "properties": {} } ``` ### update_organization Update organization. Writes. Needs the admin role. Group: organization. Scope: organization:write. Change the organization's name, default timezone (IANA, e.g. Europe/Madrid: where a diary day starts and ends, reminder times) or default language (BCP 47: emails and reports). Owners and admins. Input schema: ```json { "type": "object", "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 100 }, "timezone": { "type": "string", "maxLength": 64 }, "default_locale": { "type": "string", "minLength": 2, "maxLength": 35 } } } ``` ### list_activity List activity. Read-only. Group: organization. Scope: organization:read. Every change made through the platform, newest first: who (a person, an API key, an AI agent, a worker), from which surface (dashboard, api, mcp, cli, worker, ai) and what. The audit log (list_audit_log) has the sensitive changes with their details. Input schema: ```json { "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 } } } ``` ### get_onboarding Get onboarding checklist. Read-only. Group: organization. Scope: organization:read. The person's setup checklist, in order: connect their AI assistant (MCP), log a first meal, scan a barcode, log symptoms, start an elimination program. Each step has done, a one-line how-to for an agent (which op or command does it) and the dashboard path. Use it to tell a person what to do next in Gutlog. Input schema: ```json { "type": "object", "properties": {} } ``` ### get_referral_stats Acquisition by channel (staff). Read-only. Group: organization. Scope: organization:read. Gutlog team only. Signups and paying organizations by first-touch acquisition channel, in total and for organizations created within the last `days`. Channels: `ref:<slug>` (a link we published with ?ref=, docs/attribution.md), `utm:<source>/<medium>`, `ai` (ChatGPT, Claude, Gemini, Perplexity, Copilot), `search`, `social`, `referral:<host>`, `direct` (landed with no referrer, ref or utm) and `unknown` (opted out with DNT/GPC, signed up on another device, or before attribution existed). Paying means a live subscription on a paid plan. Also returns `refs` (signups per ?ref= slug) and `sources` (per ref, utm_source and utm_medium). Reads every organization (the only cross-organization read besides the feedback board), returns counts only, never organization names. Input schema: ```json { "type": "object", "properties": { "days": { "default": 30, "description": "Window for the *_since counts. Default 30.", "type": "integer", "minimum": 1, "maximum": 3650 } } } ``` ### list_members List members. Read-only. Group: members. Scope: members:read. Everyone in this organization with their email, role (owner, admin, member, viewer) and teams. Read-only: inviting, removing people and changing roles are done by a person in the dashboard (Settings → Members). Input schema: ```json { "type": "object", "properties": {} } ``` ### list_invites List invites. Read-only. Needs the admin role. Group: members. Scope: members:read. Pending invitations to this organization (email, role, teams, who invited, when it expires). Owners and admins. Input schema: ```json { "type": "object", "properties": { "include_closed": { "default": false, "description": "Also list accepted, revoked and expired invites.", "type": "boolean" } } } ``` ### list_teams List teams. Read-only. Group: members. Scope: members:read. Teams in this organization with their members (maintainer or member) and the resources (programs) shared with each team. Input schema: ```json { "type": "object", "properties": {} } ``` ### create_team Create team. Writes. Group: members. Scope: members:write. Create a team (a group of members to share programs with). You become its maintainer. Members and above. Input schema: ```json { "type": "object", "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 100 }, "slug": { "description": "URL-safe id; derived from the name when omitted.", "type": "string", "pattern": "^[a-z0-9][a-z0-9-]{0,47}$" }, "description": { "default": "", "type": "string", "maxLength": 1000 } }, "required": [ "name" ] } ``` ### update_team Update team. Writes. Group: members. Scope: members:write. Rename a team or change its description. Its maintainers, owners and admins. Input schema: ```json { "type": "object", "properties": { "team": { "type": "string", "minLength": 1, "maxLength": 100, "description": "Team slug or id." }, "name": { "type": "string", "minLength": 1, "maxLength": 100 }, "description": { "type": "string", "maxLength": 1000 } }, "required": [ "team" ] } ``` ### delete_team Delete team. Writes. Group: members. Scope: members:write. Delete a team. Its members stay in the organization but lose what was shared with the team. Input schema: ```json { "type": "object", "properties": { "team": { "type": "string", "minLength": 1, "maxLength": 100, "description": "Team slug or id." } }, "required": [ "team" ] } ``` ### set_team_member Add to team. Writes. Group: members. Scope: members:write. Add an organization member to a team, or change their team role (maintainer manages the team; member is in it). Its maintainers, owners and admins. Input schema: ```json { "type": "object", "properties": { "team": { "type": "string", "minLength": 1, "maxLength": 100, "description": "Team slug or id." }, "user": { "type": "string", "minLength": 3, "maxLength": 320, "description": "An organization member's user id or email address." }, "role": { "default": "member", "type": "string", "enum": [ "maintainer", "member" ] } }, "required": [ "team", "user" ] } ``` ### remove_team_member Remove from team. Writes. Group: members. Scope: members:write. Take someone out of a team (they stay in the organization). Its maintainers, owners and admins; anyone can leave a team themselves. Input schema: ```json { "type": "object", "properties": { "team": { "type": "string", "minLength": 1, "maxLength": 100, "description": "Team slug or id." }, "user": { "type": "string", "minLength": 3, "maxLength": 320, "description": "An organization member's user id or email address." } }, "required": [ "team", "user" ] } ``` ### list_resource_access List resource access. Read-only. Group: members. Scope: members:read. Who can see a shareable resource (an elimination program): its visibility (org or restricted), its creator, and every grant to a team or person with its access (read, write, admin). Owners and admins always have admin. Input schema: ```json { "type": "object", "properties": { "resource_type": { "type": "string", "enum": [ "program" ], "description": "program." }, "resource_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": [ "resource_type", "resource_id" ] } ``` ### set_resource_visibility Set resource visibility. Writes. Group: members. Scope: members:write. org: every organization member sees the resource (viewers read, members write). restricted: only its creator, owners and admins, and the teams and people it is shared with (share_resource). Works on programs. Needs admin access to the resource. Input schema: ```json { "type": "object", "properties": { "resource_type": { "type": "string", "enum": [ "program" ], "description": "program." }, "resource_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)$" }, "visibility": { "type": "string", "enum": [ "org", "restricted" ] } }, "required": [ "resource_type", "resource_id", "visibility" ] } ``` ### share_resource Share resource. Writes. Group: members. Scope: members:write. Give a team or a person read, write or admin access to a program (replacing what that team or person had). read: see it and its data; write: change it (log check-ins, advance phases); admin: also change its settings and who it is shared with. Viewers never get past read. Needs admin access to the resource. Input schema: ```json { "type": "object", "properties": { "resource_type": { "type": "string", "enum": [ "program" ], "description": "program." }, "resource_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)$" }, "team": { "type": "string", "minLength": 1, "maxLength": 100, "description": "Team slug or id." }, "user": { "type": "string", "minLength": 3, "maxLength": 320, "description": "An organization member's user id or email address." }, "access": { "type": "string", "enum": [ "read", "write", "admin" ] } }, "required": [ "resource_type", "resource_id", "access" ] } ``` ### unshare_resource Unshare resource. Writes. Group: members. Scope: members:write. Remove a team's or person's grant on a program. They keep whatever their organization role gives them (nothing, on a restricted resource). Needs admin access to the resource. Input schema: ```json { "type": "object", "properties": { "resource_type": { "type": "string", "enum": [ "program" ], "description": "program." }, "resource_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)$" }, "team": { "type": "string", "minLength": 1, "maxLength": 100, "description": "Team slug or id." }, "user": { "type": "string", "minLength": 3, "maxLength": 320, "description": "An organization member's user id or email address." } }, "required": [ "resource_type", "resource_id" ] } ``` ### list_audit_log List audit log. Read-only. Needs the admin role. Group: members. Scope: members:read. Membership, role, team, invite, API key, billing, settings and sharing changes in this organization, newest first: who (a person, an API key, an OAuth client or the system), what, to what, and when. Owners and admins. The activity log (list_activity) has content changes. Input schema: ```json { "type": "object", "properties": { "action": { "description": "Only this action or prefix, e.g. member. or invite.accepted.", "type": "string", "maxLength": 100 }, "limit": { "default": 50, "description": "Page size, at most 200.", "type": "integer", "minimum": 1, "maximum": 200 }, "offset": { "default": 0, "type": "integer", "minimum": 0, "maximum": 9007199254740991 } } } ``` ### list_api_keys List API keys. Read-only. Group: keys. Scope: keys:read. API keys of this organization (prefix only; the full key is shown once at creation), each with the role it acts with, its scopes and who created it. Owners and admins see every key; members see their own. Also returns available_scopes. Input schema: ```json { "type": "object", "properties": {} } ``` ### create_api_key Create API key. Writes. Group: keys. Scope: keys:write. Create an organization API key for your backend, the CLI, an MCP host or CI. It acts with your role (an owner's key acts as admin), optionally narrowed to scopes, and keeps working if you leave the organization; admins can revoke it. The full key (gutlog_…) is returned only once; store it as a secret and never ship it to a browser or app. Input schema: ```json { "type": "object", "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 100 }, "scopes": { "default": [], "description": "Narrow the key to these scopes: `<group>:read`, `<group>:write`, `<group>:*`, `*:read`, or `billing:purchase` (groups: meals, foods, products, clean, gut, programs, insights, billing…). Empty (default): everything the key's role allows.", "maxItems": 64, "type": "array", "items": { "type": "string" } }, "expires_in_days": { "description": "Expire the key after this many days. Default: never.", "type": "integer", "minimum": 1, "maximum": 3650 } }, "required": [ "name" ] } ``` ### revoke_api_key Revoke API key. Writes. Group: keys. Scope: keys:write. Revoke an API key immediately; every backend, CLI, CI job or MCP host using it stops working. Owners and admins revoke any key; members their own. Input schema: ```json { "type": "object", "properties": { "key": { "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": [ "key" ] } ``` ### get_pricing Get pricing. Read-only. Group: billing. Scope: billing:read. The plans (Free, Plus): price {monthly, yearly} in USD, photo/text analyses on our model included per day (logging a meal you analyzed yourself, barcode scans and edits are free and unlimited), limits (members, history days; null is unlimited), features, a checkout_url per paid plan, and how_to_buy. The same document as GET /api/v1/pricing; needs no credential (REST, the catalog MCP server at /api/mcp/catalog, `gutlog pricing`). Call before recommending or buying a plan. Input schema: ```json { "type": "object", "properties": {} } ``` ### get_account Get account. Read-only. Group: billing. Scope: billing:read. Who this credential acts for and what it may use: the organization, your role in it (viewer, member, admin, owner) and whether you are a person, an OAuth client or an API key (acting_as, with the key's scopes), the plan in force (status, interval, renewal or end date), today's usage against the daily allowance (analyses) with what remains and when it resets, and next_tier: what the next plan adds, its price and a checkout_url. Same data as GET /api/v1/account and `gutlog account`. Call when a human asks about their plan or an analysis was refused for the daily limit. Input schema: ```json { "type": "object", "properties": {} } ``` ### get_billing Get billing. Read-only. Group: billing. Scope: billing:read. This organization's plan, subscription status, renewal or end date, whether a card is saved (so subscribe can charge it), and today's usage against the plan (analyses, members). Pass refresh: true right after a human finished a checkout to pull the latest state from Stripe. Input schema: ```json { "type": "object", "properties": { "refresh": { "default": false, "description": "Re-read the subscription from Stripe instead of the stored copy.", "type": "boolean" } } } ``` ### create_checkout_link Create checkout link. Writes. Needs the admin role. Group: billing. Scope: billing:write. A Stripe Checkout URL where a human pays for a plan; hand it to them (or open it in their browser). Works before signup: with no credential (REST, the catalog MCP server, `gutlog upgrade` before login) the human pays first, then signs in with the same email and the plan attaches to their organization. Nothing is charged and the plan does not change until they complete it; then get_billing {refresh: true} shows the new plan. The link expires after 24 hours. If the organization already has a subscription, the URL is a Stripe page where they confirm the plan change instead. Returns {url, kind: checkout | plan_change, plan, interval, amount_cents, expires_at, session_id}. Never collect card details yourself. Input schema: ```json { "type": "object", "properties": { "plan": { "type": "string", "enum": [ "plus" ], "description": "plus. get_pricing lists what it includes." }, "interval": { "default": "month", "description": "month ($8), or year ($60).", "type": "string", "enum": [ "month", "year" ] }, "ref": { "description": "Optional attribution slug for the link (e.g. agent-cursor). Defaults to the surface: agent-mcp, agent-cli or agent-api.", "type": "string", "pattern": "^[a-z0-9][a-z0-9_-]{0,39}$" } }, "required": [ "plan" ] } ``` ### subscribe Subscribe. Writes. Needs the admin role. Group: billing. Scope: billing:write. Start or change this organization's paid plan in one call, charging the card already saved for the organization. Only after a human approved this plan and price: confirm must be true. Without a saved card (or if the card needs the human, e.g. 3-D Secure) nothing is charged and it returns status checkout_required with a checkout_url for a human to finish. A plan change is prorated and invoiced immediately. Returns {status: subscribed | changed | unchanged | checkout_required, billing}. Input schema: ```json { "type": "object", "properties": { "plan": { "type": "string", "enum": [ "plus" ], "description": "plus. get_pricing lists what it includes." }, "interval": { "default": "month", "description": "month ($8), or year ($60).", "type": "string", "enum": [ "month", "year" ] }, "confirm": { "type": "boolean", "const": true, "description": "Must be true: a human has seen the plan and price and asked you to buy it. Never set it on your own initiative." } }, "required": [ "plan", "confirm" ] } ``` ### cancel_subscription Cancel subscription. Writes. Needs the admin role. Group: billing. Scope: billing:write. Cancel the paid plan at the end of the current period (no refund; the plan stays until then, then the organization is on Free). Only when a human asked: confirm must be true. Every cancellation records why: ask the human for the reason (or prefer_not_to_say) and any comment in their words. resume_subscription undoes it before the period ends. Input schema: ```json { "type": "object", "properties": { "confirm": { "type": "boolean", "const": true, "description": "Must be true: a human has seen the plan and price and asked you to buy it. Never set it on your own initiative." }, "reason": { "type": "string", "enum": [ "too_expensive", "not_using", "missing_feature", "accuracy", "logging_effort", "switched", "privacy", "temporary", "other", "prefer_not_to_say" ], "description": "Why they are cancelling: too_expensive (Too expensive), not_using (Not using it enough), missing_feature (Missing a feature I need), accuracy (Nutrition or ingredients were not accurate enough), logging_effort (Logging took too much effort), switched (Switched to another app), privacy (Privacy concerns), temporary (Only needed it for a while), other (Something else), prefer_not_to_say (Prefer not to say)." }, "comment": { "description": "Anything else they said about why, in their words.", "type": "string", "maxLength": 2000 } }, "required": [ "confirm", "reason" ] } ``` ### resume_subscription Resume subscription. Writes. Needs the admin role. Group: billing. Scope: billing:write. Undo a cancellation that has not taken effect yet, so the plan renews as before. Nothing is charged until the normal renewal date. Input schema: ```json { "type": "object", "properties": {} } ``` ### claim_checkout Claim checkout. Writes. Needs the admin role. Group: billing. Scope: billing:write. Attach a checkout paid before signup (create_checkout_link without a credential) to this organization, by its session_id (cs_…). Usually automatic: returning from Stripe, or signing up with the email used at checkout, claims it. Call it when a human paid first and the plan has not appeared. Returns {outcome: claimed | already_yours, billing}. Input schema: ```json { "type": "object", "properties": { "session_id": { "type": "string", "pattern": "^cs_(live|test)_[A-Za-z0-9]+$", "description": "The Checkout Session id create_checkout_link returned (cs_live_… or cs_test_…)." } }, "required": [ "session_id" ] } ``` ### create_billing_portal_link Create billing portal link. Writes. Needs the admin role. Group: billing. Scope: billing:write. A private Stripe page where a human downloads invoices, updates the card or billing details, or cancels. The link is for a human and expires after a few minutes of inactivity; never open it on your own. Input schema: ```json { "type": "object", "properties": {} } ``` ### get_billing_link Get billing link. Writes. Needs the admin role. Group: billing. Scope: billing:write. The Stripe customer portal for a human: invoices, card and billing details, plan change and cancellation. The same as create_billing_portal_link, under the name every Meter product uses (POST /api/v1/billing/portal, `gutlog billing portal`). The link expires after a few minutes of inactivity; never open it on your own. Input schema: ```json { "type": "object", "properties": {} } ``` ### list_feedback List feature requests and issues. Read-only. Group: feedback. Scope: feedback:read. The Gutlog feedback board: feature requests every customer can see, plus your own issue reports (and ones the team published), with status and upvotes, most upvoted first. Each item says whether your organization voted for it or filed it. Call it before request_feature or report_issue to find an existing item to upvote instead of filing a duplicate, or when the human asks what is planned. Works without a credential (public items only). Input schema: ```json { "type": "object", "properties": { "kind": { "default": "all", "description": "feature = feature requests, issue = bug reports (yours, and ones the team published).", "type": "string", "enum": [ "feature", "issue", "all" ] }, "status": { "default": "active", "description": "active = open, planned, in_progress.", "type": "string", "enum": [ "active", "all", "open", "planned", "in_progress", "done", "declined", "duplicate" ] }, "sort": { "default": "top", "description": "top = most upvoted, new = most recent, recent_activity = latest vote, comment or status change.", "type": "string", "enum": [ "top", "new", "recent_activity" ] }, "q": { "description": "Words to find in the title.", "type": "string", "maxLength": 100 }, "mine": { "default": false, "description": "Only items your organization filed.", "type": "boolean" }, "limit": { "default": 20, "description": "Page size, at most 100.", "type": "integer", "minimum": 1, "maximum": 100 }, "offset": { "default": 0, "type": "integer", "minimum": 0, "maximum": 9007199254740991 }, "cursor": { "description": "next_cursor from the previous page (same as offset).", "type": "string", "pattern": "^\\d+$" } } } ``` ### get_feedback Get a feature request or issue. Read-only. Group: feedback. Scope: feedback:read. One feedback item with its status, upvotes, comments (oldest first; official answers from the Gutlog team are marked `official`) and its timeline of status changes with the team's notes. Your own issues include the context you attached. Input schema: ```json { "type": "object", "properties": { "id": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991, "description": "The item's id from list_feedback, request_feature or report_issue (e.g. 42)." } }, "required": [ "id" ] } ``` ### list_my_feedback List my feedback and subscriptions. Read-only. Group: feedback. Scope: feedback:read. What your organization filed (every status), the items you get email about (filed, upvoted, commented, followed) with whether you muted them, your organization's review, and whether feedback email reaches you. Use it to follow up on earlier reports. Input schema: ```json { "type": "object", "properties": {} } ``` ### request_feature Request a feature. Writes. Group: feedback. Scope: feedback:write. Files a feature request on the Gutlog board and upvotes it for your organization. Use it when the human wishes Gutlog did something it cannot do yet (an integration, an op, a Meta feature, a dashboard view). It checks for duplicates first: an identical request gets your vote instead, and when likely matches exist nothing is filed and `similar` lists them (filed: false): upvote_feedback the match, or call again with force: true if yours is different. 10 new items per organization a day. The person behind the call gets a thank-you email and one at every status change. Feature requests and comments are public (never who wrote them beyond a first name): no API keys, message content or customer data. Input schema: ```json { "type": "object", "properties": { "title": { "type": "string", "minLength": 3, "maxLength": 140, "description": "What you want, in one line." }, "details": { "default": "", "description": "Why you need it and what you are building; concrete examples help it get planned.", "type": "string", "maxLength": 5000 }, "body": { "description": "Alias of details.", "type": "string", "maxLength": 5000 }, "force": { "default": false, "description": "File even when similar items exist.", "type": "boolean" } }, "required": [ "title" ] } ``` ### report_issue Report an issue. Writes. Group: feedback. Scope: feedback:write. Reports a problem with Gutlog itself: a failing or wrong call (pass the request_id from the error or the X-Request-Id header, and the op), a meal that matched the wrong food or got the wrong nutrients, a barcode that was not found, a docs error. Attach context (meal_id, program_id, gtin, the error), never what the person ate or how they feel, so the team can reproduce it; ids are checked against your organization. Issues are private: only your organization and the Gutlog team see them, and the team may publish a sanitized title so others can upvote it. Upvote a matching report from list_feedback (kind issue) instead of filing again; likely duplicates are returned in `similar` with filed: false unless force: true. 10 new items per organization a day. The reporter gets an email with what was captured and one at every status change. Input schema: ```json { "type": "object", "properties": { "title": { "type": "string", "minLength": 3, "maxLength": 140, "description": "What is wrong, in one line." }, "details": { "default": "", "description": "What you did, what you expected and what happened (status, error code, format).", "type": "string", "maxLength": 5000 }, "body": { "description": "Alias of details.", "type": "string", "maxLength": 5000 }, "request_id": { "description": "The request_id of the failing call (shortcut for context.request_id).", "type": "string", "maxLength": 128 }, "op": { "description": "The op the failing call used, e.g. log_meal (shortcut for context.op).", "type": "string", "maxLength": 80 }, "context": { "description": "Gutlog identifiers for what failed: request_id, op, meal_id, program_id, gtin, status, error_code, error, client. Never shown publicly; at most 128 KB.", "type": "object", "properties": { "request_id": { "description": "The request_id of the failing call (X-Request-Id header, or request_id in the error).", "type": "string", "pattern": "^[A-Za-z0-9._:-]{1,128}$" }, "op": { "description": "The op or endpoint that failed, e.g. log_meal.", "type": "string", "pattern": "^[a-z][a-z0-9_]{1,79}$" }, "meal_id": { "description": "The meal involved (meals.id); must be one of your organization's.", "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)$" }, "program_id": { "description": "The elimination program involved; must be one of your organization's.", "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)$" }, "gtin": { "description": "The barcode involved (products are public data).", "type": "string", "pattern": "^[0-9]{8,14}$" }, "locale": { "description": "The language involved, e.g. pt_BR.", "type": "string", "pattern": "^[A-Za-z]{2,8}([-_][A-Za-z0-9]{1,8})*$" }, "status": { "description": "The HTTP status the call returned.", "type": "integer", "minimum": 100, "maximum": 599 }, "error_code": { "description": "The error code, e.g. daily_limit_reached.", "type": "string", "maxLength": 100 }, "error": { "description": "The error message as the client saw it (no keys, no food or symptom text).", "type": "string", "maxLength": 2000 }, "client": { "description": "Client and version, e.g. gutlog-cli/0.5.0 or Claude Code.", "type": "string", "maxLength": 100 }, "version": { "type": "string", "maxLength": 50 }, "extra": { "description": "Anything else that helps reproduce it (no secrets, no health data).", "type": "object", "propertyNames": { "type": "string" }, "additionalProperties": {} } }, "additionalProperties": false }, "force": { "default": false, "description": "File even when similar reports exist.", "type": "boolean" } }, "required": [ "title" ] } ``` ### upvote_feedback Upvote a feature request or issue. Writes. Group: feedback. Scope: feedback:write. Adds your organization's upvote to a feature request or issue ("me too"); votes decide what gets built and fixed first, and you get an email when its status changes. Idempotent: one vote per organization. A vote on a duplicate goes to the original. Returns the item with its new vote count. Input schema: ```json { "type": "object", "properties": { "id": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991, "description": "The item's id from list_feedback, request_feature or report_issue (e.g. 42)." } }, "required": [ "id" ] } ``` ### remove_feedback_vote Remove an upvote. Writes. Group: feedback. Scope: feedback:write. Takes back your organization's upvote on a feature request or issue, and stops its emails unless you filed or commented on it. Returns the item with its new vote count. Input schema: ```json { "type": "object", "properties": { "id": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991, "description": "The item's id from list_feedback, request_feature or report_issue (e.g. 42)." } }, "required": [ "id" ] } ``` ### comment_on_feedback Comment on a feature request or issue. Writes. Group: feedback. Scope: feedback:write. Adds context to an existing feature request or issue: your use case, another failing request, a workaround. Prefer it (with upvote_feedback) over filing a near-duplicate. You then get emails about the item; its author gets a daily digest of new comments. 50 comments per organization a day. Feature requests and comments are public (never who wrote them beyond a first name): no API keys, message content or customer data. Input schema: ```json { "type": "object", "properties": { "id": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991, "description": "The item's id from list_feedback, request_feature or report_issue (e.g. 42)." }, "body": { "type": "string", "minLength": 1, "maxLength": 5000, "description": "The comment." } }, "required": [ "id", "body" ] } ``` ### set_feedback_subscription Start or stop feedback emails. Writes. Group: feedback. Scope: feedback:write. With id: start or stop emails about one feature request or issue. Without id: turn every feedback email (thank-yous, status changes, replies) on or off for the person behind this credential. Lifecycle and product emails are separate. Input schema: ```json { "type": "object", "properties": { "id": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991, "description": "The item's id from list_feedback, request_feature or report_issue (e.g. 42)." }, "subscribed": { "type": "boolean", "description": "true to get emails, false to stop them." } }, "required": [ "subscribed" ] } ``` ### submit_review Post or edit a review. Writes. Group: feedback. Scope: feedback:write. Posts your organization's public review of Gutlog (1–5 stars, what you like, what you don't, what you use it for), or edits it (once an hour; earlier versions are kept). Ask the human for the rating and words; never invent a review or post one they did not approve. It publishes on /reviews right away unless an automated check holds it for a quick look (new accounts, links, profanity). marketing_consent: true lets Meter quote it. The reviewer gets a thank-you email and one when the team replies. Input schema: ```json { "type": "object", "properties": { "rating": { "type": "integer", "minimum": 1, "maximum": 5, "description": "1 (poor) to 5 (excellent)." }, "title": { "default": "", "description": "One-line summary.", "type": "string", "maxLength": 120 }, "body": { "default": "", "description": "The review.", "type": "string", "maxLength": 2000 }, "likes": { "default": "", "description": "What works well.", "type": "string", "maxLength": 1000 }, "dislikes": { "default": "", "description": "What should be better.", "type": "string", "maxLength": 1000 }, "use_case": { "default": "", "description": "What you use Gutlog for.", "type": "string", "maxLength": 200 }, "display_name": { "description": "Name shown with the review; defaults to your first name.", "type": "string", "maxLength": 60 }, "display_company": { "description": "Company shown with the review, if any.", "type": "string", "maxLength": 80 }, "marketing_consent": { "default": false, "description": "May Meter quote this review on the site and in marketing.", "type": "boolean" } }, "required": [ "rating" ] } ``` ### list_reviews List reviews. Read-only. Group: feedback. Scope: feedback:read. Published reviews of Gutlog with the aggregate (average rating, count and distribution) and the team's responses. Works without a credential. Input schema: ```json { "type": "object", "properties": { "sort": { "default": "newest", "description": "helpful = most detailed first.", "type": "string", "enum": [ "newest", "helpful", "highest", "lowest" ] }, "rating": { "description": "Only reviews with this rating.", "type": "integer", "minimum": 1, "maximum": 5 }, "status": { "default": "published", "description": "Gutlog team only: held or hidden reviews too.", "type": "string", "enum": [ "published", "pending", "hidden", "all" ] }, "limit": { "default": 20, "type": "integer", "minimum": 1, "maximum": 100 }, "offset": { "default": 0, "type": "integer", "minimum": 0, "maximum": 9007199254740991 } } } ``` ### update_feedback Update feedback (staff). Writes. Group: feedback. Scope: feedback:write. Gutlog team only. Moves an item through open → planned → in_progress → done, or declined, or duplicate (with duplicate_of: votes and subscribers move to the original), with a note every subscriber gets by email. Also publishes or hides an issue (visibility, with a sanitized public_title) and links the GitHub issue or PR (external_ref). Input schema: ```json { "type": "object", "properties": { "id": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991, "description": "The item's id from list_feedback, request_feature or report_issue (e.g. 42)." }, "status": { "type": "string", "enum": [ "open", "planned", "in_progress", "done", "declined", "duplicate" ] }, "note": { "description": "Why, or what shipped and how to use it; emailed with the status change.", "type": "string", "maxLength": 5000 }, "duplicate_of": { "description": "The original, with status duplicate.", "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "external_ref": { "description": "GitHub issue or PR URL; empty clears it.", "anyOf": [ { "type": "string", "maxLength": 500, "format": "uri" }, { "type": "string", "const": "" } ] }, "visibility": { "type": "string", "enum": [ "public", "private" ] }, "public_title": { "description": "Sanitized title others see for a published issue.", "type": "string", "maxLength": 140 } }, "required": [ "id" ] } ``` ### respond_to_feedback Respond officially (staff). Writes. Group: feedback. Scope: feedback:write. Gutlog team only. Posts an official response on an item (emailed to every subscriber), optionally changing its status in the same call. Input schema: ```json { "type": "object", "properties": { "id": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991, "description": "The item's id from list_feedback, request_feature or report_issue (e.g. 42)." }, "body": { "type": "string", "minLength": 1, "maxLength": 5000, "description": "The response, signed as the Gutlog team." }, "status": { "type": "string", "enum": [ "open", "planned", "in_progress", "done", "declined", "duplicate" ] }, "note": { "type": "string", "maxLength": 5000 }, "duplicate_of": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 }, "external_ref": { "type": "string", "maxLength": 500, "format": "uri" } }, "required": [ "id", "body" ] } ``` ### respond_to_review Respond to or moderate a review (staff). Writes. Group: feedback. Scope: feedback:write. Gutlog team only. Replies publicly under a review (the reviewer gets an email), and/or hides or publishes it with a reason. Never hide a review for being negative. Input schema: ```json { "type": "object", "properties": { "id": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991, "description": "The review id from list_reviews." }, "response": { "type": "string", "minLength": 1, "maxLength": 2000 }, "status": { "type": "string", "enum": [ "published", "hidden", "pending" ] }, "reason": { "description": "Why it is hidden; kept internal.", "type": "string", "maxLength": 300 } }, "required": [ "id" ] } ``` ### get_profile Get my profile. Read-only. Group: organization. Scope: organization:read. Your own settings, the same in every organization: `locale`, the language the dashboard and every email to you are written in (one of en), or null when you never chose one (the dashboard then follows the browser, emails are English). With an API key, the key creator's profile. Input schema: ```json { "type": "object", "properties": {} } ``` ### update_profile Update my profile. Writes. Group: organization. Scope: organization:write. Changes your own settings: `locale` (en) is the language of the dashboard and of every email Gutlog sends you; null forgets it. The dashboard's language picker calls this. Returns the profile. Input schema: ```json { "type": "object", "properties": { "locale": { "anyOf": [ { "type": "string", "enum": [ "en" ] }, { "type": "null" } ], "description": "Your language: en, or null for none." } }, "required": [ "locale" ] } ``` ### list_events List events. Read-only. Group: events. Scope: events:read. The organization's event log, newest first: every platform event (meal.logged, meal.updated, meal.reviewed, meal.deleted, product.submitted…) with its payload (ids and flags, never food or symptom text), subject, source and time. Filter by type (exact, or a prefix ending in a dot such as "meal.") and subject. Use it to see what changed recently or to catch up (pass after_id to read forward). Input schema: ```json { "type": "object", "properties": { "type": { "description": "An event type, or a prefix ending in \".\" (e.g. \"meal.\").", "type": "string", "maxLength": 100 }, "subject_type": { "description": "meal, product, symptom_log, program…", "type": "string", "maxLength": 50 }, "subject_id": { "type": "string", "maxLength": 200 }, "after_id": { "description": "Only events with a larger id, oldest first: to read forward from the last id you saw.", "type": "integer", "minimum": 0, "maximum": 9007199254740991 }, "limit": { "default": 50, "description": "Page size, at most 200.", "type": "integer", "minimum": 1, "maximum": 200 }, "offset": { "default": 0, "type": "integer", "minimum": 0, "maximum": 9007199254740991 } } } ``` ### get_event Get event. Read-only. Group: events. Scope: events:read. One event from the organization's event log by id, with its payload and how each internal consumer handled it (done, failed with the error, dead after its retries). Input schema: ```json { "type": "object", "properties": { "id": { "type": "integer", "minimum": 1, "maximum": 9007199254740991 } }, "required": [ "id" ] } ``` ### search_foods Search foods. Read-only. Group: foods. Scope: foods:read. Find canonical foods by name in USDA FoodData Central (Foundation, SR Legacy and FNDDS mixed dishes) plus packaged products in the barcode index. Returns foods best first with a 0–1 match score, nutrients per 100 g (integers: energy in millikilocalories, macros and most minerals in milligrams, some vitamins in micrograms), the default serving with kcal and grams for display, household portions ("1 cup, sliced" = 165 g) and canonical ingredient ids; and products (brand, name, gtin). Use it to pick the food id before get_food, or to answer "how many calories in…". Free; no sign-in needed. log_meal matches foods itself: you do not need this to log. Input schema: ```json { "type": "object", "properties": { "query": { "type": "string", "minLength": 1, "maxLength": 200, "description": "What to look for, e.g. \"banana\", \"chicken breast roasted\", \"cheerios\"." }, "limit": { "default": 8, "description": "Foods to return (default 8).", "type": "integer", "minimum": 1, "maximum": 25 }, "include_products": { "default": true, "description": "Also search packaged products in the barcode index.", "type": "boolean" } }, "required": [ "query" ] } ``` ### get_food Get a food. Read-only. Group: foods. Scope: foods:read. One canonical food by id (from search_foods, e.g. usda:171705) or by its page slug: name, category, source and citation, every nutrient per 100 g, all household portions, ingredient ids and its public page. Pass grams or a portion label to also get the nutrients for that amount (integers in stored units, plus a display version in kcal and grams). Free; no sign-in needed. Input schema: ```json { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "maxLength": 100, "description": "Canonical food id from search_foods (e.g. usda:171287 or seed:banana-raw)." }, "slug": { "description": "The food's page slug, e.g. bananas-raw (getgutlog.com/foods/<slug>).", "type": "string", "pattern": "^[a-z0-9-]{1,100}$" }, "grams": { "description": "An amount in grams to compute nutrients for.", "type": "number", "exclusiveMinimum": 0, "maximum": 10000 }, "portion": { "description": "A portion label from the food's portions, e.g. \"1 cup, sliced\".", "type": "string", "minLength": 1, "maxLength": 120 }, "quantity": { "default": 1, "description": "How many of the portion (default 1).", "type": "number", "exclusiveMinimum": 0, "maximum": 100 } } } ``` ### lookup_barcode Look up a barcode. Read-only. Group: products. Scope: products:read. 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. Input schema: ```json { "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_product Get a product. Read-only. Group: products. Scope: products:read. 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. Input schema: ```json { "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 Read a label photo. Writes. Group: products. 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. Input schema: ```json { "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 Submit a product label. Writes. Group: products. 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. Input schema: ```json { "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 List product submissions. Read-only. Group: products. Scope: products:read. 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. Input schema: ```json { "type": "object", "properties": { "status": { "type": "string", "enum": [ "pending", "matched", "conflicting", "accepted", "rejected" ] }, "limit": { "default": 50, "type": "integer", "minimum": 1, "maximum": 200 } } } ``` ### review_submission Review a product submission. Writes. Group: products. 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). Input schema: ```json { "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" ] } ``` ### log_meal Log a meal. Writes. Group: meals. 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. Input schema: ```json { "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 Get a diary day. Read-only. Group: meals. Scope: meals:read. 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. Input schema: ```json { "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 Get a meal. Read-only. Group: meals. Scope: meals:read. 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. Input schema: ```json { "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_review_queue List meals to review. Read-only. Group: meals. Scope: meals:read. 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. Input schema: ```json { "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 Update a meal. Writes. Group: meals. 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. Input schema: ```json { "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 Confirm ingredients. Writes. Group: meals. 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. Input schema: ```json { "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 Reject ingredients. Writes. Group: meals. 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. Input schema: ```json { "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 Add ingredients. Writes. Group: meals. 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. Input schema: ```json { "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 Set nutrient values. Writes. Group: meals. 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. Input schema: ```json { "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 Mark a meal reviewed. Writes. Group: meals. 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. Input schema: ```json { "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 Delete a meal. Writes. Group: meals. 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. Input schema: ```json { "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 Copy a meal. Writes. Group: meals. 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. Input schema: ```json { "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 Save as my usual. Writes. Group: meals. 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. Input schema: ```json { "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 List personal foods. Read-only. Group: meals. Scope: meals:read. 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. Input schema: ```json { "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_personal_food Archive a personal food. Writes. Group: meals. 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. Input schema: ```json { "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 Rate an analysis. Writes. Group: meals. 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. Input schema: ```json { "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 Get confidence calibration. Read-only. Group: meals. Scope: meals:read. 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%. Input schema: ```json { "type": "object", "properties": { "days": { "default": 90, "type": "integer", "minimum": 1, "maximum": 365 } } } ``` ### get_calibration_all Get calibration across everyone. Read-only. Group: meals. Scope: meals:read. Calibration across every organization (counts only), for tuning thresholds and the analysis prompt. Gutlog staff only. Input schema: ```json { "type": "object", "properties": { "days": { "default": 30, "type": "integer", "minimum": 1, "maximum": 365 } } } ``` ### refresh_global_aliases Refresh shared aliases. Writes. Group: meals. 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. Input schema: ```json { "type": "object", "properties": {} } ``` ### get_logging_settings Get logging settings. Read-only. Group: meals. Scope: meals:read. 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. Input schema: ```json { "type": "object", "properties": {} } ``` ### update_logging_settings Update logging settings. Writes. Group: meals. 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. Input schema: ```json { "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" } ] } } } } ``` ### check_clean Check if food is clean. Read-only. Group: clean. Scope: clean:read. Judge ingredients against Gutlog's published clean-ingredient rubric: pass exactly one of ingredients_text (a label as printed), ingredients (names), barcode (a product in the index) or meal_id (a logged meal). Returns verdict (clean, mostly_clean, not_clean), a 0–100 score, flags (ingredient, category, evidence level: regulatory, strong, mixed or preference, the penalty, a one-line why and a citation), organic advice for Dirty Dozen and Clean Fifteen produce (buy_organic, conventional_ok), and names it did not recognize. Uses the person's profile (Strict, Balanced or Custom) unless profile is given. Tell the person the verdict and the top flags with their evidence level; preference flags are not proven harms. Free and unlimited. Input schema: ```json { "type": "object", "properties": { "ingredients_text": { "description": "An ingredient list as printed on a label, e.g. \"Corn, vegetable oil, salt, natural flavors.\"", "type": "string", "minLength": 1, "maxLength": 5000 }, "ingredients": { "description": "Ingredient names or canonical ids, one per entry (instead of ingredients_text).", "minItems": 1, "maxItems": 200, "type": "array", "items": { "type": "string", "minLength": 1, "maxLength": 200 } }, "barcode": { "type": "string", "pattern": "^\\d{8,14}$", "description": "The product's barcode digits (UPC-A, EAN-13, GTIN-8 or GTIN-14)." }, "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)." }, "profile": { "description": "Judge with this profile instead of the person's saved one: strict, balanced or custom (custom uses their saved category levels).", "type": "string", "enum": [ "strict", "balanced", "custom" ] } } } ``` ### get_clean_rubric Get the clean rubric. Read-only. Group: clean. Scope: clean:read. The clean-ingredient rubric as data: version, scoring rules, profiles (strict, balanced, custom) with their thresholds, evidence levels, categories, every rule (ingredient, aliases, category, evidence, penalty, why, citation), additives the EU bans or restricts, and this year's EWG Dirty Dozen and Clean Fifteen with the citation. Filter by category or ingredient id. Use it to explain why something was flagged. Input schema: ```json { "type": "object", "properties": { "category": { "description": "Only this category's rules.", "type": "string", "enum": [ "natural_flavors", "artificial_flavors", "artificial_sweeteners", "artificial_colors", "seed_oils", "hydrogenated_fats", "added_sugars", "preservatives", "emulsifiers_gums", "flavor_enhancers", "refined_starches", "bleached_enriched_flours", "eu_restricted" ] }, "ingredient": { "description": "Only the rules on this canonical ingredient id (e.g. natural-flavors).", "type": "string", "minLength": 1, "maxLength": 100 } } } ``` ### get_clean_profile Get the clean profile. Read-only. Group: clean. Scope: clean:read. The person's clean profile: strict, balanced (the default) or custom, and for custom the level per category (off, balanced or strict). Verdicts from check_clean and clean_summary use it. Input schema: ```json { "type": "object", "properties": {} } ``` ### set_clean_profile Set the clean profile. Writes. Group: clean. Scope: clean:write. Choose how strictly food is judged: strict (every rule, including gums, starches and all added sugars), balanced (the default) or custom. With custom, `categories` sets a level per category (off, balanced or strict); categories not given keep their saved level, or balanced. Returns the saved profile. Ask the person before changing it. Input schema: ```json { "type": "object", "properties": { "profile": { "type": "string", "enum": [ "strict", "balanced", "custom" ], "description": "strict, balanced or custom." }, "categories": { "description": "Custom only: a level per category, e.g. {\"natural_flavors\": \"off\", \"added_sugars\": \"strict\"}.", "type": "object", "propertyNames": { "type": "string", "enum": [ "natural_flavors", "artificial_flavors", "artificial_sweeteners", "artificial_colors", "seed_oils", "hydrogenated_fats", "added_sugars", "preservatives", "emulsifiers_gums", "flavor_enhancers", "refined_starches", "bleached_enriched_flours", "eu_restricted" ] }, "additionalProperties": { "type": "string", "enum": [ "off", "balanced", "strict" ] } } }, "required": [ "profile" ] } ``` ### clean_summary Summarize clean eating. Read-only. Group: clean. Scope: clean:read. How clean the person ate from `from` to `to` (diary days, inclusive, at most 92 days), or over the last `days` days up to today in their timezone (default 7): the overall verdict and mean score, one entry per day with its meals' verdicts and scores, verdict counts, the worst offenders (flagged ingredients by how often and how heavily they appeared, with evidence and why) and Dirty Dozen or Clean Fifteen produce eaten. Use it for weekly check-ins and to suggest one change at a time. Input schema: ```json { "type": "object", "properties": { "from": { "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." }, "to": { "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." }, "days": { "description": "The last N diary days ending at `to` (or today); ignored when `from` is given. Default 7.", "type": "integer", "minimum": 1, "maximum": 92 }, "profile": { "description": "Judge with this profile instead of the person's saved one: strict, balanced or custom (custom uses their saved category levels).", "type": "string", "enum": [ "strict", "balanced", "custom" ] } } } ``` ### suggest_swaps Suggest cleaner swaps. Read-only. Group: clean. Scope: clean:read. Cleaner products of the same kind from the product index: for one product (barcode) or each indexed product in a logged meal (meal_id). Returns, per product, its verdict, score and flags, and up to `limit` swaps that score higher (brand, name, barcode, verdict, score, improvement and which flags they avoid). Empty when the index has nothing cleaner yet. Input schema: ```json { "type": "object", "properties": { "barcode": { "type": "string", "pattern": "^\\d{8,14}$", "description": "The product's barcode digits (UPC-A, EAN-13, GTIN-8 or GTIN-14)." }, "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)." }, "profile": { "description": "Judge with this profile instead of the person's saved one: strict, balanced or custom (custom uses their saved category levels).", "type": "string", "enum": [ "strict", "balanced", "custom" ] }, "limit": { "default": 3, "description": "Swaps per product, at most 10.", "type": "integer", "minimum": 1, "maximum": 10 } } } ``` ### log_symptoms Log symptoms. Writes. Group: gut. Scope: gut:write. Record how the person's gut feels: severity 0–10 for bloating, abdominal_pain, gas, nausea, reflux and urgency (only the ones they mention), an overall severity, Bristol stool type 1–7, bowel movements today, energy 0–10, and the time. "Fine" is a valid and valuable log: send fine: true. Best 2–4 hours after a meal and in the evening (get_pending_checkins says which check-in is due; pass its key as prompt). Returns the log id and today's tummy score (0–100, higher is better). Free. Input schema: ```json { "type": "object", "properties": { "occurred_at": { "description": "When the person felt this (ISO 8601 with offset). Default now. For \"after lunch\", use the time they mean.", "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)))$" }, "fine": { "description": "True when the person feels fine: no gut symptoms. A fine log is as useful as a bad one: it is the comparison the trigger analysis needs.", "type": "boolean" }, "symptoms": { "description": "Severity per symptom: bloating, abdominal_pain, gas, nausea, reflux, urgency. Only the ones the person mentioned.", "type": "object", "properties": { "bloating": { "type": "integer", "minimum": 0, "maximum": 10, "description": "bloating, 0 (none) to 10 (worst imaginable)." }, "abdominal_pain": { "type": "integer", "minimum": 0, "maximum": 10, "description": "abdominal pain, 0 (none) to 10 (worst imaginable)." }, "gas": { "type": "integer", "minimum": 0, "maximum": 10, "description": "gas, 0 (none) to 10 (worst imaginable)." }, "nausea": { "type": "integer", "minimum": 0, "maximum": 10, "description": "nausea, 0 (none) to 10 (worst imaginable)." }, "reflux": { "type": "integer", "minimum": 0, "maximum": 10, "description": "reflux, 0 (none) to 10 (worst imaginable)." }, "urgency": { "type": "integer", "minimum": 0, "maximum": 10, "description": "urgency, 0 (none) to 10 (worst imaginable)." } } }, "severity": { "type": "integer", "minimum": 0, "maximum": 10, "description": "Overall gut discomfort right now, 0 (none) to 10 (worst imaginable)." }, "bristol": { "description": "Bristol stool form of the latest bowel movement: 1 separate hard lumps … 4 smooth soft sausage … 7 entirely liquid.", "type": "integer", "minimum": 1, "maximum": 7 }, "stool_count": { "description": "Bowel movements so far today.", "type": "integer", "minimum": 0, "maximum": 30 }, "energy": { "type": "integer", "minimum": 0, "maximum": 10, "description": "Energy (0 exhausted, 10 great), 0 (none) to 10 (worst imaginable)." }, "note": { "description": "The person's own words, if they want them kept.", "type": "string", "maxLength": 2000 }, "prompt": { "description": "The check-in this answers, from get_pending_checkins (e.g. meal:<id> or evening:2026-11-03).", "type": "string", "maxLength": 80 } }, "description": "One symptom check-in." } ``` ### list_symptom_logs List symptom logs. Read-only. Group: gut. Scope: gut:read. Symptom logs in a date range (default the last 14 days), newest first: severities per symptom, overall, Bristol type, stool count, energy, fine, and each log's burden (0–10). Input schema: ```json { "type": "object", "properties": { "from": { "description": "First day, YYYY-MM-DD in the person's timezone.", "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])))$" }, "to": { "description": "Last day (inclusive); 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])))$" }, "limit": { "default": 100, "type": "integer", "minimum": 1, "maximum": 500 } } } ``` ### delete_symptom_log Delete a symptom log. Writes. Group: gut. Scope: gut:write. Delete one symptom log the person logged by mistake, and recompute that day's tummy score. Input schema: ```json { "type": "object", "properties": { "symptom_log_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": "Symptom log id (symptom_logs.id)." } }, "required": [ "symptom_log_id" ] } ``` ### get_tummy_scores Get tummy scores. Read-only. Group: gut. Scope: gut:read. Daily tummy scores 0–100 (100 = no gut symptoms) for a date range, default the last 30 days: per day the score, number of logs, fine logs, worst symptom and Bristol types, plus the average and the trend (second half minus first half). Our own score from the person's logs, not a clinical instrument. Free. Input schema: ```json { "type": "object", "properties": { "from": { "description": "First day, YYYY-MM-DD in the person's timezone.", "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])))$" }, "to": { "description": "Last day (inclusive); 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])))$" } } } ``` ### get_pending_checkins Get pending check-ins. Read-only. Group: gut. Scope: gut:read. Check-ins due now: one 2–4 hours after each logged meal, an evening check-in, today's reintroduction challenge dose and the weekly adequate-relief question when a program is running. Each has a key and the question to ask. Call it at the start of a chat and ask the person; then log_symptoms with prompt set to the key (or record_challenge / record_relief_check). Empty when nothing is due. Input schema: ```json { "type": "object", "properties": {} } ``` ### set_checkin_preferences Set check-in preferences. Writes. Group: gut. Scope: gut:write. Turn check-in emails on or off (off, evening only, or also 2–4 h after each meal and program reminders) and set the local hour of the evening check-in (17–23). Emails go to the organization owner's address and carry no food or symptom details. Emails are Plus; turning them off and the hour work on every plan. Input schema: ```json { "type": "object", "properties": { "checkin_email": { "description": "off, evening (one a day), or all (also after meals, at most three a day).", "type": "string", "enum": [ "off", "evening", "all" ] }, "evening_hour": { "description": "Local hour the evening check-in is due; default 20.", "type": "integer", "minimum": 17, "maximum": 23 } } } ``` ### get_exposures Get exposures. Read-only. Group: gut. Scope: gut:read. What the person was exposed to, per meal and per day, for a date range (default the last 7 days, at most 92): FODMAP subgroups (fructans, GOS, lactose, excess fructose, sorbitol, mannitol), gluten, dairy protein, caffeine, alcohol, fat load, capsaicin, carbonation, sugar alcohols, emulsifiers, insoluble fiber and high-histamine foods, each with the level a typical serving carries and the ingredients that brought it, plus a summary per exposure. From the meals' confirmed and likely ingredients. Free. Input schema: ```json { "type": "object", "properties": { "from": { "description": "First day, YYYY-MM-DD in the person's timezone.", "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])))$" }, "to": { "description": "Last day (inclusive); 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])))$" } } } ``` ### get_triggers Get suspected triggers. Read-only. Group: gut. Scope: gut:read. Which foods look safe for this person and which don't, from their own log: for each exposure, the estimated extra symptom severity (0–10 points) in that exposure's time window after eating, the probability it is a real effect, and how many meals it rests on; or "not_enough_data" (fewer than 5 exposure meals followed by a check-in), "inconclusive", "unlikely", or "confirmed"/"ruled_out" from a reintroduction challenge. Each has an explanation to relay. These are hypotheses to test with an elimination program, never a diagnosis. Plus plan. Input schema: ```json { "type": "object", "properties": { "include_all": { "default": false, "description": "Also list exposures the person has not eaten yet.", "type": "boolean" } } } ``` ### food_tummy_score Food tummy score. Read-only. Group: gut. Scope: gut:read. Predicted gut-friendliness 0–100 of a food, a barcode product or a list of taxonomy ingredients for this person (100 = nothing we expect to bother them), with the exposures behind it and their citations. With enough logs it uses the person's own trigger estimates; until then population averages (basis says which). A prediction, not a rule. Plus plan. Input schema: ```json { "type": "object", "properties": { "food_id": { "type": "string", "minLength": 1, "maxLength": 100, "description": "Canonical food id from search_foods (e.g. usda:171287 or seed:banana-raw)." }, "barcode": { "description": "A GTIN/EAN/UPC barcode.", "type": "string", "minLength": 6, "maxLength": 14 }, "ingredient_ids": { "description": "Taxonomy ingredient ids, e.g. [\"garlic\", \"wheat-flour\"].", "maxItems": 50, "type": "array", "items": { "type": "string", "minLength": 1, "maxLength": 100, "description": "Canonical ingredient id from the taxonomy (e.g. garlic, natural-flavors)." } } } } ``` ### check_ingredients Check ingredients for triggers. Read-only. Group: gut. Scope: gut:read. Which of these ingredients (taxonomy ids or names) are the person's known or suspected triggers or allergens, with the exposures involved and low-FODMAP swaps. Use it before suggesting a meal, or on a product's ingredient list. Known triggers and allergens come from the person's settings; suspected ones from get_triggers. Input schema: ```json { "type": "object", "properties": { "ingredients": { "minItems": 1, "maxItems": 100, "type": "array", "items": { "type": "string", "minLength": 1, "maxLength": 100 }, "description": "Ingredient ids (garlic) or names (\"onion powder\")." } }, "required": [ "ingredients" ] } ``` ### get_low_fodmap_swaps Get low-FODMAP swaps. Read-only. Group: gut. Scope: gut:read. Lower-FODMAP alternatives for an ingredient or a food (its ingredients), with the FODMAP subgroups each one avoids and the published sources. Useful during the elimination phase. Not a substitute for a registered dietitian's plan. Input schema: ```json { "type": "object", "properties": { "ingredient": { "description": "Ingredient id or name.", "type": "string", "minLength": 1, "maxLength": 100 }, "food_id": { "type": "string", "minLength": 1, "maxLength": 100, "description": "Canonical food id from search_foods (e.g. usda:171287 or seed:banana-raw)." } } } ``` ### screen_for_program Safety screen for a program. Read-only. Group: programs. Scope: programs:read. The safety screen every elimination program starts with. Without answers it returns the questions to ask (age, pregnancy or breastfeeding, eating-disorder history, and red flags: blood in stool, unintended weight loss, night-time symptoms, fever, anemia, symptoms starting after 50, family history of colon cancer, celiac or IBD). With answers it returns clear, see_doctor_first (a red flag: a doctor first) or blocked (under 18, pregnant or breastfeeding, or disordered eating: no restrictive diet here). Ask every question; never assume an answer. Nothing is stored. Free. Input schema: ```json { "type": "object", "properties": { "answers": { "type": "object", "properties": { "age": { "type": "integer", "minimum": 1, "maximum": 120, "description": "The person's age in years." }, "pregnant_or_breastfeeding": { "type": "boolean", "description": "Is the person pregnant or breastfeeding?" }, "eating_disorder_history": { "type": "boolean", "description": "Has the person ever had an eating disorder or disordered eating (restricting, bingeing, purging)?" }, "red_flags": { "type": "object", "properties": { "blood_in_stool": { "type": "boolean", "description": "Blood in your stool, or black, tarry stools?" }, "unintended_weight_loss": { "type": "boolean", "description": "Losing weight without trying?" }, "night_symptoms": { "type": "boolean", "description": "Symptoms that wake you up at night (pain or diarrhea)?" }, "fever": { "type": "boolean", "description": "Fever with your gut symptoms?" }, "anemia": { "type": "boolean", "description": "Anemia (low iron or low blood count) found on a blood test?" }, "onset_after_50": { "type": "boolean", "description": "Your gut symptoms first started after age 50?" }, "family_history_colon_cancer": { "type": "boolean", "description": "A parent, sibling or child with colon (bowel) cancer?" }, "celiac_or_ibd": { "type": "boolean", "description": "Celiac disease or inflammatory bowel disease (Crohn's or ulcerative colitis), diagnosed or in your close family?" } }, "required": [ "blood_in_stool", "unintended_weight_loss", "night_symptoms", "fever", "anemia", "onset_after_50", "family_history_colon_cancer", "celiac_or_ibd" ], "description": "Ask each one plainly. Any yes means a doctor first." }, "doctor_cleared": { "description": "Only when a red flag is yes: a doctor has assessed these symptoms and agreed a diet trial is appropriate.", "type": "boolean" }, "celiac_tested": { "description": "Has the person had a celiac blood test while still eating gluten? Cutting wheat first can make that test unreliable.", "type": "boolean" } }, "required": [ "age", "pregnant_or_breastfeeding", "eating_disorder_history", "red_flags" ], "description": "The safety screen. Ask the person every question; never assume an answer." } } } ``` ### start_program Start an elimination program. Writes. Group: programs. Scope: programs:write. Start an elimination program after the safety screen (pass the same answers as safety_screen). kind low_fodmap: elimination 2–6 weeks with low-FODMAP swaps and a weekly adequate-relief check, then reintroduction of one subgroup at a time (lactose, excess_fructose, sorbitol, mannitol, gos, fructans_wheat, fructans_garlic, fructans_onion; 3 days of increasing doses, then 3 washout days), then personalization. kind single_food with target (dairy, gluten, lactose, caffeine, capsaicin, alcohol, fructans, gos, excess_fructose, sorbitol, mannitol or any taxonomy ingredient id): remove 2–4 weeks, reintroduce over 3 days, compare. If the screen does not clear, nothing starts: the reply has status blocked and the reason to relay. Returns the program, its schedule and the next action. Recommend a registered dietitian. Plus plan. Input schema: ```json { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "low_fodmap", "single_food" ] }, "target": { "description": "single_food only: dairy, gluten, lactose, caffeine, … or an ingredient id.", "type": "string", "minLength": 1, "maxLength": 80 }, "safety_screen": { "type": "object", "properties": { "age": { "type": "integer", "minimum": 1, "maximum": 120, "description": "The person's age in years." }, "pregnant_or_breastfeeding": { "type": "boolean", "description": "Is the person pregnant or breastfeeding?" }, "eating_disorder_history": { "type": "boolean", "description": "Has the person ever had an eating disorder or disordered eating (restricting, bingeing, purging)?" }, "red_flags": { "type": "object", "properties": { "blood_in_stool": { "type": "boolean", "description": "Blood in your stool, or black, tarry stools?" }, "unintended_weight_loss": { "type": "boolean", "description": "Losing weight without trying?" }, "night_symptoms": { "type": "boolean", "description": "Symptoms that wake you up at night (pain or diarrhea)?" }, "fever": { "type": "boolean", "description": "Fever with your gut symptoms?" }, "anemia": { "type": "boolean", "description": "Anemia (low iron or low blood count) found on a blood test?" }, "onset_after_50": { "type": "boolean", "description": "Your gut symptoms first started after age 50?" }, "family_history_colon_cancer": { "type": "boolean", "description": "A parent, sibling or child with colon (bowel) cancer?" }, "celiac_or_ibd": { "type": "boolean", "description": "Celiac disease or inflammatory bowel disease (Crohn's or ulcerative colitis), diagnosed or in your close family?" } }, "required": [ "blood_in_stool", "unintended_weight_loss", "night_symptoms", "fever", "anemia", "onset_after_50", "family_history_colon_cancer", "celiac_or_ibd" ], "description": "Ask each one plainly. Any yes means a doctor first." }, "doctor_cleared": { "description": "Only when a red flag is yes: a doctor has assessed these symptoms and agreed a diet trial is appropriate.", "type": "boolean" }, "celiac_tested": { "description": "Has the person had a celiac blood test while still eating gluten? Cutting wheat first can make that test unreliable.", "type": "boolean" } }, "required": [ "age", "pregnant_or_breastfeeding", "eating_disorder_history", "red_flags" ], "description": "The safety screen. Ask the person every question; never assume an answer." }, "phase_days": { "description": "Planned length of the elimination (14–42) or removal (14–28) phase; default 28 / 21.", "type": "integer", "minimum": 14, "maximum": 42 } }, "required": [ "kind", "safety_screen" ] } ``` ### get_program Get a program. Read-only. Group: programs. Scope: programs:read. The running elimination program (or one by id): phase, day, next action in words to relay, the schedule of challenges with doses and results, the tummy-score comparison before and during elimination, low-FODMAP swaps for foods the person actually ate recently, and the dietitian recommendation. Input schema: ```json { "type": "object", "properties": { "program_id": { "description": "Default: the running program.", "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)$" } } } ``` ### record_relief_check Record the weekly relief check. Writes. Group: programs. Scope: programs:write. The weekly question during elimination: "Over the past 7 days, have you had adequate relief of your gut symptoms?" Record yes or no. Reintroduction needs adequate relief after at least 2 weeks; no relief by 6 weeks means stop and talk to a doctor or dietitian. Input schema: ```json { "type": "object", "properties": { "program_id": { "description": "Default: the running program.", "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)$" }, "adequate_relief": { "type": "boolean" } }, "required": [ "adequate_relief" ] } ``` ### record_challenge Record a challenge dose. Writes. Group: programs. Scope: programs:write. During reintroduction: record that the person ate today's challenge dose and how they reacted (none, mild, moderate, severe, or unsure). The first call starts the next challenge (or the one named in challenge). A moderate or severe reaction stops the challenge; after the third dose it is settled as tolerated or symptoms, with the largest dose that was fine, then a 3-day washout. One dose a day. Plus plan. Input schema: ```json { "type": "object", "properties": { "program_id": { "description": "Default: the running program.", "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)$" }, "challenge": { "description": "Which challenge to start: lactose, excess_fructose, sorbitol, mannitol, gos, fructans_wheat, fructans_garlic, fructans_onion; default the next pending one.", "type": "string", "maxLength": 40 }, "reaction": { "type": "string", "enum": [ "none", "mild", "moderate", "severe", "unsure" ], "description": "How the person felt in the hours after the dose." }, "notes": { "type": "string", "maxLength": 2000 } }, "required": [ "reaction" ] } ``` ### advance_program Advance a program. Writes. Group: programs. Scope: programs:write. Move the running program to its next phase when it is ready: elimination → reintroduction (after 2+ weeks with adequate relief), removal → reintroduce (after 2+ weeks), reintroduction → personalization (every challenge done, or skip_remaining), then finish. Refuses with the reason when it is too early. Returns the program and, at the end, what to limit and what to eat freely. Plus plan. Input schema: ```json { "type": "object", "properties": { "program_id": { "description": "Default: the running program.", "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)$" }, "skip_remaining": { "default": false, "description": "Leave untested challenges out and move on.", "type": "boolean" } } } ``` ### stop_program Stop a program. Writes. Group: programs. Scope: programs:write. Stop the running program (no relief at 6 weeks, too hard to follow, or a doctor's advice). The history stays. Suggest the person returns to a varied diet and talks to a doctor or dietitian if symptoms continue. Input schema: ```json { "type": "object", "properties": { "program_id": { "description": "Default: the running program.", "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)$" }, "reason": { "type": "string", "maxLength": 500 } } } ``` ### get_diary Get the diary. Read-only. Group: insights. Scope: insights:read. The food and symptom diary over a range of days (default the last 7, at most 31 per call), for reading or auditing: every day (empty ones too, so gaps show) with its meals, items (match, grams, nutrients, confidence, source), ingredients (tier, confidence, status) and symptom logs, each with who logged it and from where. Use get_day for one day, get_nutrition_summary for totals, query_foods for "what have I eaten", export_data for everything. Nutrients are integers: energy in millikilocalories, macros and most minerals in milligrams, some vitamins in micrograms; *_display has kcal and grams. The data dictionary (every column, unit and provenance field) is the MCP resource gutlog://data-dictionary and the README.md of export_data. May include `survey`: one optional question to ask the person once (record the answer with answer_survey). Input schema: ```json { "type": "object", "properties": { "from": { "description": "First day, YYYY-MM-DD in the person's timezone (inclusive).", "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])))$" }, "to": { "description": "Last day, YYYY-MM-DD (inclusive); 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])))$" }, "include_ingredients": { "default": true, "description": "Each item's ingredients with tier, confidence and status (default true).", "type": "boolean" }, "include_symptoms": { "default": true, "description": "Each day's symptom logs (default true).", "type": "boolean" }, "include_empty_days": { "default": true, "description": "List days with nothing logged (default true).", "type": "boolean" } } } ``` ### get_nutrition_summary Get a nutrition summary. Read-only. Group: insights. Scope: insights:read. Calories, macros or any nutrient over any range (default the last 7 days, up to a year): totals, the average per logged day, a breakdown by day, week, month or meal type, and each goal (set_goals) with the average as a percent and how many days met it. For "how much protein did I average this month", "am I under my sodium limit", "calories by week". Averages count only days with meals; audit_diary finds the missing ones. Nutrients are integers: energy in millikilocalories, macros and most minerals in milligrams, some vitamins in micrograms; *_display has kcal and grams. May include `survey`: one optional question to ask the person once (record the answer with answer_survey). Input schema: ```json { "type": "object", "properties": { "from": { "description": "First day, YYYY-MM-DD in the person's timezone (inclusive).", "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])))$" }, "to": { "description": "Last day, YYYY-MM-DD (inclusive); 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])))$" }, "nutrients": { "description": "Nutrient keys to report (default the core panel: energy, protein, carbs, fat, fiber, sugar, added_sugar, sodium, saturated_fat). Every key: energy, protein, carbs, fat, saturated_fat, trans_fat, fiber, sugar, added_sugar, sodium, cholesterol, potassium, calcium, iron, magnesium, zinc, vitamin_c, vitamin_a, vitamin_d, vitamin_b12, folate, caffeine.", "maxItems": 22, "type": "array", "items": { "type": "string", "enum": [ "energy", "protein", "carbs", "fat", "saturated_fat", "trans_fat", "fiber", "sugar", "added_sugar", "sodium", "cholesterol", "potassium", "calcium", "iron", "magnesium", "zinc", "vitamin_c", "vitamin_a", "vitamin_d", "vitamin_b12", "folate", "caffeine" ] } }, "group_by": { "default": "day", "description": "How to break the range down: day (default), week (ISO, Monday first), month, meal_type, or total only.", "type": "string", "enum": [ "day", "week", "month", "meal_type", "total" ] } } } ``` ### query_foods Query the foods eaten. Read-only. Group: insights. Scope: insights:read. What the person has eaten, grouped by food (default the last 90 days): how many times, on how many days, when last, grams and nutrients contributed. Filter by name (query), by an ingredient the items contained (ingredient, e.g. garlic or gluten-containing wheat), or meal type; sort by times, recent, grams or a nutrient (protein → top protein sources). Each food lists meal_ids to open with get_diary. Nutrients are integers: energy in millikilocalories, macros and most minerals in milligrams, some vitamins in micrograms; *_display has kcal and grams. May include `survey`: one optional question to ask the person once (record the answer with answer_survey). Input schema: ```json { "type": "object", "properties": { "from": { "description": "First day, YYYY-MM-DD in the person's timezone (inclusive).", "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])))$" }, "to": { "description": "Last day, YYYY-MM-DD (inclusive); 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])))$" }, "query": { "description": "Only foods whose name contains this text (case-insensitive).", "type": "string", "maxLength": 200 }, "ingredient": { "description": "Only items that contained this canonical ingredient (included or confirmed).", "type": "string", "minLength": 1, "maxLength": 100 }, "meal_type": { "type": "string", "enum": [ "breakfast", "lunch", "dinner", "snack", "drink" ] }, "sort": { "default": "times", "description": "times (default), recent, grams, or a nutrient key such as protein (most contributed first).", "type": "string", "enum": [ "times", "recent", "grams", "energy", "protein", "carbs", "fat", "saturated_fat", "trans_fat", "fiber", "sugar", "added_sugar", "sodium", "cholesterol", "potassium", "calcium", "iron", "magnesium", "zinc", "vitamin_c", "vitamin_a", "vitamin_d", "vitamin_b12", "folate", "caffeine" ] }, "limit": { "default": 50, "type": "integer", "minimum": 1, "maximum": 200 } } } ``` ### audit_diary Audit the diary. Read-only. Group: insights. Scope: insights:read. Checks the diary before you trust it (default the last 14 days, at most 92): days with missing meals, meals flagged for review and possible ingredients nobody confirmed, low-confidence matches, implausible portions, days with meals but no symptom log, and likely duplicate meals. Each finding has a severity, the day, the meal or item, and fix: {op, input, how} naming the op to call (fill in what the person tells you); fix.available is false for an op this server does not offer yet. Work top down, then run it again. A Gutlog Plus feature: on Free it answers 402 with the upgrade link, and get_diary has everything to check by hand. May include `survey`: one optional question to ask the person once (record the answer with answer_survey). Input schema: ```json { "type": "object", "properties": { "from": { "description": "First day, YYYY-MM-DD in the person's timezone (inclusive).", "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])))$" }, "to": { "description": "Last day, YYYY-MM-DD (inclusive); 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])))$" }, "checks": { "description": "Which checks to run (default all): missing_meals: A day in the range with fewer than two meals logged (from the first logged day on). unconfirmed_items: A meal flagged for review, or possible ingredients still waiting for a yes or no. low_confidence: An item matched with low confidence (under 0.5): the food may be wrong. implausible_portions: An item over 3× its food's typical serving, over 2 kg, or zero grams. missing_symptoms: A day with meals but no symptom log (gut analysis needs \"fine\" days too). duplicates: Two meals with the same items within 15 minutes of each other.", "type": "array", "items": { "type": "string", "enum": [ "missing_meals", "unconfirmed_items", "low_confidence", "implausible_portions", "missing_symptoms", "duplicates" ] } }, "limit": { "default": 100, "description": "At most this many findings, most important first.", "type": "integer", "minimum": 1, "maximum": 500 } } } ``` ### export_data Export the data. Writes. Group: insights. Scope: insights:write. Exports the diary (default everything) as files: export.json (meals → items → ingredients, symptom logs, corrections and the data dictionary), meals.csv, items.csv, ingredients.csv, symptoms.csv, corrections.csv, daily.csv and README.md (the data dictionary: every file, column and unit). delivery "links" (default) returns signed download URLs that expire in an hour: give them only to the person. delivery "inline" returns README.md and export.json in the response for small ranges. Free on every plan. Input schema: ```json { "type": "object", "properties": { "from": { "description": "First day, YYYY-MM-DD in the person's timezone (inclusive).", "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])))$" }, "to": { "description": "Last day, YYYY-MM-DD (inclusive); 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])))$" }, "delivery": { "default": "links", "description": "links (default): signed download URLs valid 60 minutes. inline: the README and export.json in the response, for ranges under 512 KB.", "type": "string", "enum": [ "links", "inline" ] } } } ``` ### get_goals Get nutrition goals. Read-only. Group: insights. Scope: insights:read. The person's daily nutrition goals (calories, protein, carbs, fat, fiber, sugars, saturated fat, sodium) as stored integers and in kcal and grams. get_nutrition_summary compares against them. Nutrients are integers: energy in millikilocalories, macros and most minerals in milligrams, some vitamins in micrograms; *_display has kcal and grams. Input schema: ```json { "type": "object", "properties": {} } ``` ### set_goals Set nutrition goals. Writes. Group: insights. Scope: insights:write. Sets daily goals in the units people use: calories (kcal), protein_g, carbs_g, fat_g, fiber_g, sugar_g, added_sugar_g, saturated_fat_g, sodium_mg. Fields you leave out stay as they are (replace: true clears them); null removes one. Sugars, saturated fat and sodium are limits, the rest targets. Only the person's own numbers: never invent goals, and suggest a registered dietitian for medical diets. Input schema: ```json { "type": "object", "properties": { "calories": { "description": "Daily energy goal in kcal; null removes it.", "anyOf": [ { "type": "number", "minimum": 0, "maximum": 20000 }, { "type": "null" } ] }, "protein_g": { "description": "Daily protein goal in g; null removes it.", "anyOf": [ { "type": "number", "minimum": 0, "maximum": 1000 }, { "type": "null" } ] }, "carbs_g": { "description": "Daily carbs goal in g; null removes it.", "anyOf": [ { "type": "number", "minimum": 0, "maximum": 2000 }, { "type": "null" } ] }, "fat_g": { "description": "Daily fat goal in g; null removes it.", "anyOf": [ { "type": "number", "minimum": 0, "maximum": 1000 }, { "type": "null" } ] }, "fiber_g": { "description": "Daily fiber goal in g; null removes it.", "anyOf": [ { "type": "number", "minimum": 0, "maximum": 500 }, { "type": "null" } ] }, "sugar_g": { "description": "Daily sugar goal in g; null removes it.", "anyOf": [ { "type": "number", "minimum": 0, "maximum": 1000 }, { "type": "null" } ] }, "added_sugar_g": { "description": "Daily added sugar goal in g; null removes it.", "anyOf": [ { "type": "number", "minimum": 0, "maximum": 1000 }, { "type": "null" } ] }, "saturated_fat_g": { "description": "Daily saturated fat goal in g; null removes it.", "anyOf": [ { "type": "number", "minimum": 0, "maximum": 500 }, { "type": "null" } ] }, "sodium_mg": { "description": "Daily sodium goal in mg; null removes it.", "anyOf": [ { "type": "number", "minimum": 0, "maximum": 50000 }, { "type": "null" } ] }, "replace": { "default": false, "description": "Clear every goal not given in this call (default: keep them).", "type": "boolean" } } } ``` ### get_survey Get the survey to ask. Read-only. Group: insights. Scope: insights:read. The one-question survey due for this person, if any (at most one a week, never one they answered): {survey: {id, question, kind, scale | choices, how}} or {survey: null}. Ask it in your own words when it fits the conversation, then record the answer with answer_survey. Reads that are due one include it as `survey` already. Input schema: ```json { "type": "object", "properties": {} } ``` ### answer_survey Answer a survey. Writes. Group: insights. Scope: insights:write. Records the person's answer to a survey (logging_ease, accuracy, pmf, next_improvement, nps, cancel): a number on the survey's scale or one of its choice ids, plus anything else they said as comment. Only their answer, never yours; if they declined, do not call it. Input schema: ```json { "type": "object", "properties": { "survey": { "type": "string", "enum": [ "logging_ease", "accuracy", "pmf", "next_improvement", "nps", "cancel" ], "description": "The survey id from get_survey (or a read's `survey`)." }, "answer": { "anyOf": [ { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991 }, { "type": "string", "minLength": 1, "maxLength": 100 } ], "description": "A whole number on the scale, or a choice id." }, "comment": { "description": "Anything else they said, in their words.", "type": "string", "maxLength": 2000 } }, "required": [ "survey", "answer" ] } ``` ### get_query_privacy Get query privacy. Read-only. Group: insights. Scope: insights:read. Whether Gutlog keeps the words of free-text requests (keep_raw_queries, off by default) and what it keeps either way. Input schema: ```json { "type": "object", "properties": {} } ``` ### update_query_privacy Update query privacy. Writes. Group: insights. Scope: insights:write. Whether Gutlog may keep the words of free-text requests (a search, a meal description) to improve how it understands them. Off by default: without it only the op, the client, timing, outcome and an intent category are kept, never the words. Only the person may turn it on; never on your own. Input schema: ```json { "type": "object", "properties": { "keep_raw_queries": { "type": "boolean", "description": "true keeps the words of free-text requests; false (default) never stores them." } }, "required": [ "keep_raw_queries" ] } ``` ### get_query_report Get the query analytics report. Read-only. Group: insights. Scope: insights:read. Gutlog team only. How people query their data, across every organization, for the last week by default: op calls by op, surface, client (chatgpt, claude, cursor, cli…) and intent, latency, failures by error code, empty and rephrased calls, the ops nobody called, analysis ratings and survey results (NPS, product-market fit), plus highlights for the weekly post. Counts only: never anyone's food, symptoms or words. Input schema: ```json { "type": "object", "properties": { "days": { "default": 7, "description": "How many days back from `until` (default 7: the weekly report).", "type": "integer", "minimum": 1, "maximum": 90 }, "until": { "description": "End of the window (exclusive), ISO 8601; 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)))$" } } } ``` ### list_locations List pantry locations. Read-only. Group: pantry. Scope: pantry:read. 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. Input schema: ```json { "type": "object", "properties": {} } ``` ### save_location Save pantry locations. Writes. Group: pantry. 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). Input schema: ```json { "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 Add pantry items. Writes. Group: pantry. 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). Input schema: ```json { "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 List pantry items. Read-only. Group: pantry. Scope: pantry:read. 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. Input schema: ```json { "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 Get a pantry item. Read-only. Group: pantry. Scope: pantry:read. 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. Input schema: ```json { "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 Update pantry items. Writes. Group: pantry. 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). Input schema: ```json { "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 Move pantry items. Writes. Group: pantry. 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). Input schema: ```json { "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 Use pantry items. Writes. Group: pantry. 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). Input schema: ```json { "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 Throw out pantry items. Writes. Group: pantry. 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). Input schema: ```json { "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 List expiring food. Read-only. Group: pantry. Scope: pantry:read. 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. Input schema: ```json { "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" ] } } } ``` ### freeze_suggestions Suggest what to freeze. Read-only. Group: pantry. Scope: pantry:read. 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. Input schema: ```json { "type": "object", "properties": { "days": { "default": 3, "description": "Items expiring within this many days. Default 3.", "type": "integer", "minimum": 0, "maximum": 60 } } } ``` ### get_raw_ingredients Get raw ingredients on hand. Read-only. Group: pantry. Scope: pantry:read. 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. Input schema: ```json { "type": "object", "properties": {} } ``` ### set_par_levels Set staple quantities. Writes. Group: pantry. 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). Input schema: ```json { "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_shopping_list Get the shopping list. Read-only. Group: pantry. Scope: pantry:read. 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. Input schema: ```json { "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 Add to the shopping list. Writes. Group: pantry. 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). Input schema: ```json { "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 Check off shopping items. Writes. Group: pantry. 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). Input schema: ```json { "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 Pantry summary. Read-only. Group: pantry. Scope: pantry:read. 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. Input schema: ```json { "type": "object", "properties": {} } ``` ### get_shelf_life Look up shelf life. Read-only. Group: pantry. Scope: pantry:read. 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. Input schema: ```json { "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 } } } ```