{
  "openapi": "3.1.0",
  "info": {
    "title": "NutrientGraph API",
    "version": "0.1.0",
    "summary": "Foods, branded products and dishes with a source on every value; recipe calculation.",
    "description": "Every response carries the dataset version and provenance. Values are copied by code from primary sources; where an AI judged something, the method and confidence are recorded.\n\nAuthentication: send your key in the `X-API-Key` header. Quotas are monthly units; most lookups cost 1 unit.\n\nPlans and prices: see the pricing page.",
    "license": {
      "name": "Proprietary; data licences per source in meta.sources"
    }
  },
  "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
  "servers": [
    {
      "url": "https://api.nutrientgraph.com",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "Food",
      "description": "Foods, branded products and dishes with per-value provenance"
    },
    {
      "name": "Account",
      "description": "Signup, account, keys, datasets and usage"
    },
    {
      "name": "MCP",
      "description": "Model Context Protocol server for AI agents"
    }
  ],
  "paths": {
    "/v1/food/search": {
      "get": {
        "operationId": "food_search",
        "tags": [
          "Food"
        ],
        "summary": "Search foods by name",
        "description": "Full-text search (prefix matching) over food names in English, French, German, Italian or Spanish: 15,961 generic foods (USDA, CIQUAL), 430,443 branded products (USDA Branded Foods, English) and 150 dishes. Without a type, generic foods and dishes come before branded products. Returns summaries; fetch a food by id for all values and provenance.\n\nQuota cost: 1 unit. MCP tool: `food_search`.",
        "x-units": 1,
        "x-mcp-tool": "food_search",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "description": "Search text",
            "schema": {
              "type": "string",
              "minLength": 2,
              "maxLength": 200
            },
            "example": "chicken breast roasted"
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "Language of the name; non-English names of USDA foods are machine translations (marked as such)",
            "schema": {
              "type": "string",
              "enum": [
                "en",
                "fr",
                "de",
                "it",
                "es",
                "any"
              ],
              "default": "en"
            },
            "example": "en"
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "generic",
                "branded",
                "dish"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 10
            },
            "example": 3
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK. `data` holds the result; `meta` holds the dataset version, sources and usage.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests per minute allowed for this key",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current minute",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Limit": {
                "description": "Units per month in the plan",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Used": {
                "description": "Units used this month (including this call on success)",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Dataset-Version": {
                "description": "Version of the dataset that answered",
                "schema": {
                  "type": "string"
                }
              },
              "X-Request-Id": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Valid request that cannot be computed (e.g. out of coverage)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or monthly quota exceeded",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests per minute allowed for this key",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current minute",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Limit": {
                "description": "Units per month in the plan",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Used": {
                "description": "Units used this month (including this call on success)",
                "schema": {
                  "type": "integer"
                }
              },
              "Retry-After": {
                "description": "Seconds until the rate window resets (rate limit only)",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Dataset not loaded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/food/foods/{id}": {
      "get": {
        "operationId": "food_get",
        "tags": [
          "Food"
        ],
        "summary": "Get a food by id",
        "description": "All nutrients per 100 g (or per 100 ml for products labelled per ml) with per-value provenance (source dataset, record id, source nutrient id, retrieval date, method), names in five languages with how each was made, portions with gram weights and their sources, and flags. Dishes include their recipe, cooking-yield tier and the computation trail.\n\nQuota cost: 1 unit. MCP tool: `food_get`.",
        "x-units": 1,
        "x-mcp-tool": "food_get",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "fdc:<FDC id>, ciqual:<code>, gtin:<GTIN-14> or dish:<id>",
            "schema": {
              "type": "string",
              "pattern": "^(fdc|ciqual|gtin|dish):[A-Za-z0-9_.-]{1,40}$"
            },
            "example": "fdc:171477"
          }
        ],
        "responses": {
          "200": {
            "description": "OK. `data` holds the result; `meta` holds the dataset version, sources and usage.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests per minute allowed for this key",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current minute",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Limit": {
                "description": "Units per month in the plan",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Used": {
                "description": "Units used this month (including this call on success)",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Dataset-Version": {
                "description": "Version of the dataset that answered",
                "schema": {
                  "type": "string"
                }
              },
              "X-Request-Id": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Valid request that cannot be computed (e.g. out of coverage)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or monthly quota exceeded",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests per minute allowed for this key",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current minute",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Limit": {
                "description": "Units per month in the plan",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Used": {
                "description": "Units used this month (including this call on success)",
                "schema": {
                  "type": "integer"
                }
              },
              "Retry-After": {
                "description": "Seconds until the rate window resets (rate limit only)",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Dataset not loaded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/food/barcode/{gtin}": {
      "get": {
        "operationId": "food_barcode",
        "tags": [
          "Food"
        ],
        "summary": "Look up a packaged product by barcode",
        "description": "GTIN-8, UPC-A (12), EAN-13 or GTIN-14. The check digit is validated and the code normalised to GTIN-14. Returns the product from USDA Branded Foods with the FDC records behind it.\n\nQuota cost: 1 unit. MCP tool: `food_barcode`.",
        "x-units": 1,
        "x-mcp-tool": "food_barcode",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "gtin",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[0-9 -]{8,20}$"
            },
            "example": "016000228986"
          }
        ],
        "responses": {
          "200": {
            "description": "OK. `data` holds the result; `meta` holds the dataset version, sources and usage.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests per minute allowed for this key",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current minute",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Limit": {
                "description": "Units per month in the plan",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Used": {
                "description": "Units used this month (including this call on success)",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Dataset-Version": {
                "description": "Version of the dataset that answered",
                "schema": {
                  "type": "string"
                }
              },
              "X-Request-Id": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Valid request that cannot be computed (e.g. out of coverage)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or monthly quota exceeded",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests per minute allowed for this key",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current minute",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Limit": {
                "description": "Units per month in the plan",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Used": {
                "description": "Units used this month (including this call on success)",
                "schema": {
                  "type": "integer"
                }
              },
              "Retry-After": {
                "description": "Seconds until the rate window resets (rate limit only)",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Dataset not loaded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/food/foods/{id}/nutrients": {
      "get": {
        "operationId": "food_nutrients",
        "tags": [
          "Food"
        ],
        "summary": "Nutrients for a portion",
        "description": "Scales per-100 g values to a portion: give grams, or a portion (index or description from the food's portions) and a quantity. Missing values stay null. Products labelled per 100 ml are refused (density unknown).\n\nQuota cost: 1 unit. MCP tool: `food_nutrients_for_portion`.",
        "x-units": 1,
        "x-mcp-tool": "food_nutrients_for_portion",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "fdc:<FDC id>, ciqual:<code>, gtin:<GTIN-14> or dish:<id>",
            "schema": {
              "type": "string",
              "pattern": "^(fdc|ciqual|gtin|dish):[A-Za-z0-9_.-]{1,40}$"
            },
            "example": "fdc:171477"
          },
          {
            "name": "grams",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number",
              "exclusiveMinimum": 0,
              "maximum": 100000
            }
          },
          {
            "name": "portion",
            "in": "query",
            "required": false,
            "description": "Portion index (0-based) or description",
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "example": "0"
          },
          {
            "name": "quantity",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number",
              "exclusiveMinimum": 0,
              "maximum": 1000,
              "default": 1
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "OK. `data` holds the result; `meta` holds the dataset version, sources and usage.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests per minute allowed for this key",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current minute",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Limit": {
                "description": "Units per month in the plan",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Used": {
                "description": "Units used this month (including this call on success)",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Dataset-Version": {
                "description": "Version of the dataset that answered",
                "schema": {
                  "type": "string"
                }
              },
              "X-Request-Id": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Valid request that cannot be computed (e.g. out of coverage)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or monthly quota exceeded",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests per minute allowed for this key",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current minute",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Limit": {
                "description": "Units per month in the plan",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Used": {
                "description": "Units used this month (including this call on success)",
                "schema": {
                  "type": "integer"
                }
              },
              "Retry-After": {
                "description": "Seconds until the rate window resets (rate limit only)",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Dataset not loaded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/food/recipe": {
      "post": {
        "operationId": "food_recipe",
        "tags": [
          "Food"
        ],
        "summary": "Calculate a recipe",
        "description": "Nutrients per 100 g as eaten (and per serving) of a recipe from ingredient foods and grams, with the food dataset's recipe engine (the code that computed its 150 dishes). Weight changes come only from cited sources you name: a cooked record of the same food (convert.to_cooked, by dry-matter balance), FNDDS moisture change, a USDA meat/poultry yield row, a verified AH-102 row, or an SR refuse portion; no yield is ever assumed (T6) and the result's tier says which kind was used. A nutrient missing for an ingredient is taken from a USDA crosswalk-linked record of the same food, else the result is null with a lower bound (never zero, T7). Nutrient numbers in the request are rejected.\n\nQuota cost: 1 unit per 5 ingredients (minimum 1). MCP tool: `food_recipe_calculate`.",
        "x-units": "1 unit per 5 ingredients (minimum 1)",
        "x-mcp-tool": "food_recipe_calculate",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "cooked",
                  "ingredients"
                ],
                "additionalProperties": false,
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "cooked": {
                    "type": "boolean",
                    "description": "false: no heat step (tier T0); true: cooked (T1-T3 from the references used)"
                  },
                  "servings": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 1000
                  },
                  "ingredients": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 60,
                    "items": {
                      "type": "object",
                      "required": [
                        "food"
                      ],
                      "additionalProperties": false,
                      "properties": {
                        "food": {
                          "type": "string",
                          "pattern": "^(fdc|ciqual|gtin|fdc_branded):[0-9A-Za-z]{1,20}$",
                          "description": "Ingredient food: fdc:<FDC id>, ciqual:<code>, gtin:<GTIN-14> (per-100 g products only) or fdc_branded:<FDC id>"
                        },
                        "grams": {
                          "type": "number",
                          "exclusiveMinimum": 0,
                          "maximum": 100000,
                          "description": "Grams as written in the recipe (raw weight)"
                        },
                        "label": {
                          "type": "string",
                          "maxLength": 200
                        },
                        "convert": {
                          "type": "object",
                          "additionalProperties": false,
                          "description": "Enter the ingredient as a cooked record of the same food",
                          "properties": {
                            "to_cooked": {
                              "type": "string",
                              "pattern": "^(fdc|ciqual|gtin|fdc_branded):[0-9A-Za-z]{1,20}$",
                              "description": "Ingredient food: fdc:<FDC id>, ciqual:<code>, gtin:<GTIN-14> (per-100 g products only) or fdc_branded:<FDC id>"
                            },
                            "yield": {
                              "type": "object",
                              "additionalProperties": false,
                              "description": "Weight raw -> cooked from a cited yield (else dry-matter balance between the two records)",
                              "properties": {
                                "fndds_moisture": {
                                  "type": "integer",
                                  "description": "FNDDS 2021-2023 food code: its recipe moisture change"
                                },
                                "meat_yield_row": {
                                  "type": "integer",
                                  "description": "Row of the USDA Table of Cooking Yields for Meat and Poultry"
                                },
                                "fat": {
                                  "type": "string",
                                  "enum": [
                                    "drained",
                                    "kept"
                                  ],
                                  "description": "meat_yield_row: 'drained' removes the fat change (only when the method drains fat)"
                                },
                                "ah102_item": {
                                  "type": "integer",
                                  "description": "Verified row of USDA Agriculture Handbook 102"
                                },
                                "evaporate_all_water": {
                                  "type": "boolean",
                                  "description": "All water of the ingredient cooks off (flagged as estimated, T3)"
                                },
                                "match": {
                                  "type": "string",
                                  "enum": [
                                    "same_food",
                                    "analog"
                                  ],
                                  "description": "analog: the yield of a similar food (flags the result T3 estimated)"
                                },
                                "sr_refuse_portion": {
                                  "type": "object",
                                  "required": [
                                    "food",
                                    "seq"
                                  ],
                                  "additionalProperties": false,
                                  "properties": {
                                    "food": {
                                      "type": "string",
                                      "pattern": "^(fdc|ciqual|gtin|fdc_branded):[0-9A-Za-z]{1,20}$",
                                      "description": "Ingredient food: fdc:<FDC id>, ciqual:<code>, gtin:<GTIN-14> (per-100 g products only) or fdc_branded:<FDC id>"
                                    },
                                    "seq": {
                                      "type": "integer",
                                      "minimum": 1
                                    }
                                  },
                                  "description": "SR 'yield from 1 lb raw' portion"
                                }
                              }
                            },
                            "edible_ah102": {
                              "type": "integer",
                              "description": "AH-102 item for the edible portion (refuse removed)"
                            },
                            "match": {
                              "type": "string",
                              "enum": [
                                "same_food",
                                "analog"
                              ]
                            }
                          }
                        },
                        "cook": {
                          "type": "object",
                          "additionalProperties": false,
                          "description": "One cited weight-change source (from GET /v1/food/cooking-yields): exactly one of fndds_moisture, meat_yield_row, ah102_item, evaporate_all_water",
                          "properties": {
                            "fndds_moisture": {
                              "type": "integer",
                              "description": "FNDDS 2021-2023 food code: its recipe moisture change"
                            },
                            "meat_yield_row": {
                              "type": "integer",
                              "description": "Row of the USDA Table of Cooking Yields for Meat and Poultry"
                            },
                            "fat": {
                              "type": "string",
                              "enum": [
                                "drained",
                                "kept"
                              ],
                              "description": "meat_yield_row: 'drained' removes the fat change (only when the method drains fat)"
                            },
                            "ah102_item": {
                              "type": "integer",
                              "description": "Verified row of USDA Agriculture Handbook 102"
                            },
                            "evaporate_all_water": {
                              "type": "boolean",
                              "description": "All water of the ingredient cooks off (flagged as estimated, T3)"
                            },
                            "match": {
                              "type": "string",
                              "enum": [
                                "same_food",
                                "analog"
                              ],
                              "description": "analog: the yield of a similar food (flags the result T3 estimated)"
                            }
                          }
                        },
                        "retention_code": {
                          "type": "integer",
                          "minimum": 1,
                          "description": "USDA Release 6 nutrient retention code (minerals; alcohol with its energy)"
                        },
                        "grams_rule": {
                          "type": "object",
                          "required": [
                            "fndds_ratio"
                          ],
                          "additionalProperties": false,
                          "description": "Grams from an FNDDS recipe ratio (e.g. water absorbed per gram of rice)",
                          "properties": {
                            "fndds_ratio": {
                              "type": "object",
                              "required": [
                                "food_code",
                                "ingredient_codes",
                                "per_codes",
                                "of"
                              ],
                              "additionalProperties": false,
                              "properties": {
                                "food_code": {
                                  "type": "integer"
                                },
                                "ingredient_codes": {
                                  "type": "array",
                                  "items": {
                                    "type": "integer"
                                  }
                                },
                                "per_codes": {
                                  "type": "array",
                                  "items": {
                                    "type": "integer"
                                  }
                                },
                                "of": {
                                  "type": "array",
                                  "items": {
                                    "type": "integer",
                                    "minimum": 0
                                  },
                                  "description": "0-based indexes of earlier ingredients the ratio applies to"
                                }
                              }
                            },
                            "match": {
                              "type": "string",
                              "enum": [
                                "same_food",
                                "analog"
                              ]
                            }
                          }
                        }
                      }
                    }
                  },
                  "hydrate": {
                    "type": "array",
                    "maxItems": 10,
                    "items": {
                      "type": "object",
                      "required": [
                        "members",
                        "like"
                      ],
                      "additionalProperties": false,
                      "properties": {
                        "members": {
                          "type": "array",
                          "items": {
                            "type": "integer",
                            "minimum": 0
                          }
                        },
                        "like": {
                          "type": "string",
                          "pattern": "^(fdc|ciqual|gtin|fdc_branded):[0-9A-Za-z]{1,20}$",
                          "description": "Ingredient food: fdc:<FDC id>, ciqual:<code>, gtin:<GTIN-14> (per-100 g products only) or fdc_branded:<FDC id>"
                        },
                        "match": {
                          "type": "string",
                          "enum": [
                            "same_food",
                            "analog"
                          ]
                        }
                      }
                    },
                    "description": "Bring a group of ingredients to the water content of a cooked record (e.g. a dough)"
                  },
                  "group_cook": {
                    "type": "array",
                    "maxItems": 10,
                    "items": {
                      "type": "object",
                      "required": [
                        "members",
                        "cook"
                      ],
                      "additionalProperties": false,
                      "properties": {
                        "members": {
                          "type": "array",
                          "items": {
                            "type": "integer",
                            "minimum": 0
                          }
                        },
                        "cook": {
                          "type": "object",
                          "additionalProperties": false,
                          "description": "One cited weight-change source (from GET /v1/food/cooking-yields): exactly one of fndds_moisture, meat_yield_row, ah102_item, evaporate_all_water",
                          "properties": {
                            "fndds_moisture": {
                              "type": "integer",
                              "description": "FNDDS 2021-2023 food code: its recipe moisture change"
                            },
                            "meat_yield_row": {
                              "type": "integer",
                              "description": "Row of the USDA Table of Cooking Yields for Meat and Poultry"
                            },
                            "fat": {
                              "type": "string",
                              "enum": [
                                "drained",
                                "kept"
                              ],
                              "description": "meat_yield_row: 'drained' removes the fat change (only when the method drains fat)"
                            },
                            "ah102_item": {
                              "type": "integer",
                              "description": "Verified row of USDA Agriculture Handbook 102"
                            },
                            "evaporate_all_water": {
                              "type": "boolean",
                              "description": "All water of the ingredient cooks off (flagged as estimated, T3)"
                            },
                            "match": {
                              "type": "string",
                              "enum": [
                                "same_food",
                                "analog"
                              ],
                              "description": "analog: the yield of a similar food (flags the result T3 estimated)"
                            }
                          }
                        }
                      }
                    },
                    "description": "Weight change of a group of ingredients (e.g. a sauce reduced)"
                  },
                  "dish_cook": {
                    "type": "object",
                    "additionalProperties": false,
                    "description": "One cited weight-change source (from GET /v1/food/cooking-yields): exactly one of fndds_moisture, meat_yield_row, ah102_item, evaporate_all_water",
                    "properties": {
                      "fndds_moisture": {
                        "type": "integer",
                        "description": "FNDDS 2021-2023 food code: its recipe moisture change"
                      },
                      "meat_yield_row": {
                        "type": "integer",
                        "description": "Row of the USDA Table of Cooking Yields for Meat and Poultry"
                      },
                      "fat": {
                        "type": "string",
                        "enum": [
                          "drained",
                          "kept"
                        ],
                        "description": "meat_yield_row: 'drained' removes the fat change (only when the method drains fat)"
                      },
                      "ah102_item": {
                        "type": "integer",
                        "description": "Verified row of USDA Agriculture Handbook 102"
                      },
                      "evaporate_all_water": {
                        "type": "boolean",
                        "description": "All water of the ingredient cooks off (flagged as estimated, T3)"
                      },
                      "match": {
                        "type": "string",
                        "enum": [
                          "same_food",
                          "analog"
                        ],
                        "description": "analog: the yield of a similar food (flags the result T3 estimated)"
                      }
                    }
                  },
                  "uncovered_heat_steps": {
                    "type": "array",
                    "maxItems": 10,
                    "items": {
                      "type": "string",
                      "maxLength": 200
                    },
                    "description": "Heat steps you know of that have no USDA yield (flags the result T3)"
                  },
                  "retention_code": {
                    "type": "integer",
                    "minimum": 1
                  }
                }
              },
              "example": {
                "name": "scrambled eggs on buttered toast",
                "cooked": true,
                "servings": 1,
                "ingredients": [
                  {
                    "food": "fdc:748967",
                    "grams": 100,
                    "label": "egg, whole, raw (2 large)"
                  },
                  {
                    "food": "fdc:171265",
                    "grams": 30,
                    "label": "whole milk"
                  },
                  {
                    "food": "fdc:173410",
                    "grams": 10,
                    "label": "butter, salted"
                  },
                  {
                    "food": "fdc:2707598",
                    "grams": 60,
                    "label": "white sandwich bread (2 slices), toasted",
                    "convert": {
                      "to_cooked": "fdc:2707599"
                    }
                  },
                  {
                    "food": "fdc:173468",
                    "grams": 1,
                    "label": "table salt"
                  }
                ],
                "dish_cook": {
                  "fndds_moisture": 32130020,
                  "match": "same_food"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK. `data` holds the result; `meta` holds the dataset version, sources and usage.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests per minute allowed for this key",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current minute",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Limit": {
                "description": "Units per month in the plan",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Used": {
                "description": "Units used this month (including this call on success)",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Dataset-Version": {
                "description": "Version of the dataset that answered",
                "schema": {
                  "type": "string"
                }
              },
              "X-Request-Id": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body over 512 KB",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Body is not application/json",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Valid request that cannot be computed (e.g. out of coverage)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or monthly quota exceeded",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests per minute allowed for this key",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current minute",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Limit": {
                "description": "Units per month in the plan",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Used": {
                "description": "Units used this month (including this call on success)",
                "schema": {
                  "type": "integer"
                }
              },
              "Retry-After": {
                "description": "Seconds until the rate window resets (rate limit only)",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Dataset not loaded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/food/cooking-yields": {
      "get": {
        "operationId": "food_cooking_yields",
        "tags": [
          "Food"
        ],
        "summary": "Find cooking-yield references",
        "description": "Rows of the USDA Table of Cooking Yields for Meat and Poultry, verified rows of USDA Agriculture Handbook 102 and FNDDS recipe moisture changes, each with the `reference` to use in a recipe.\n\nQuota cost: 1 unit. MCP tool: `food_cooking_yields`.",
        "x-units": 1,
        "x-mcp-tool": "food_cooking_yields",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 100
            },
            "example": "ground beef"
          },
          {
            "name": "source",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "meat",
                "ah102",
                "fndds"
              ]
            },
            "example": "meat"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            },
            "example": 3
          }
        ],
        "responses": {
          "200": {
            "description": "OK. `data` holds the result; `meta` holds the dataset version, sources and usage.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests per minute allowed for this key",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current minute",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Limit": {
                "description": "Units per month in the plan",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Used": {
                "description": "Units used this month (including this call on success)",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Dataset-Version": {
                "description": "Version of the dataset that answered",
                "schema": {
                  "type": "string"
                }
              },
              "X-Request-Id": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Valid request that cannot be computed (e.g. out of coverage)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or monthly quota exceeded",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests per minute allowed for this key",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current minute",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Limit": {
                "description": "Units per month in the plan",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Used": {
                "description": "Units used this month (including this call on success)",
                "schema": {
                  "type": "integer"
                }
              },
              "Retry-After": {
                "description": "Seconds until the rate window resets (rate limit only)",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Dataset not loaded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/datasets": {
      "get": {
        "operationId": "datasets_list",
        "tags": [
          "Account"
        ],
        "summary": "Datasets and versions",
        "security": [],
        "description": "Datasets served, their versions, record counts, sources and licences, and load report.",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/v1/health": {
      "get": {
        "operationId": "health",
        "tags": [
          "Account"
        ],
        "summary": "Service health",
        "security": [],
        "description": "Status, the tariff engine's coverage, and the loaded dataset versions.",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/v1/datasets/{name}/snapshot": {
      "get": {
        "operationId": "datasets_snapshot",
        "tags": [
          "Account"
        ],
        "summary": "Bulk snapshot (Enterprise)",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "gzip of the dataset's data.sqlite for the loaded version. Enterprise plan only. Free (0 units).",
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "food"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "gzip file",
            "content": {
              "application/gzip": {
                "schema": {
                  "type": "string",
                  "contentMediaType": "application/gzip"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Plan does not include snapshots",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown dataset or no snapshot stored",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/signup": {
      "post": {
        "operationId": "signup",
        "tags": [
          "Account"
        ],
        "summary": "Ask for a free API key (sends a verification email)",
        "security": [],
        "description": "Step 1 of 2. Checks the Cloudflare Turnstile token, refuses disposable email domains and applies per-IP and per-email limits, then emails a single-use link valid 30 minutes. No key is issued yet. The 202 answer is the same whether or not the address already has an account. Off unless SIGNUP_ENABLED.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "accept_terms",
                  "turnstile_token"
                ],
                "additionalProperties": false,
                "properties": {
                  "email": {
                    "type": "string",
                    "maxLength": 254
                  },
                  "name": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "accept_terms": {
                    "type": "boolean",
                    "const": true
                  },
                  "turnstile_token": {
                    "type": "string",
                    "maxLength": 2048,
                    "description": "The token from the Turnstile widget on the signup page"
                  },
                  "product": {
                    "type": "string",
                    "enum": [
                      "tariffs",
                      "food",
                      "cosmetics"
                    ],
                    "description": "The product site the signup came from (the verification link points back to it)"
                  },
                  "attribution": {
                    "type": "object",
                    "additionalProperties": false,
                    "description": "Captured by the site when the visitor landed: UTM tags, document.referrer and the landing page URL. Only the referrer's host and the landing page's path are kept.",
                    "properties": {
                      "utm_source": {
                        "type": "string"
                      },
                      "utm_medium": {
                        "type": "string"
                      },
                      "utm_campaign": {
                        "type": "string"
                      },
                      "utm_term": {
                        "type": "string"
                      },
                      "utm_content": {
                        "type": "string"
                      },
                      "referrer": {
                        "type": "string"
                      },
                      "landing_page": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Verification email sent"
          },
          "400": {
            "description": "Invalid or disposable email, terms not accepted, or no Turnstile token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Signup is not open, or the human check failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts from this IP, email or account (Retry-After header)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Bot protection or email is not configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/signup/verify": {
      "post": {
        "operationId": "signup_verify",
        "tags": [
          "Account"
        ],
        "summary": "Confirm the email and get the free key",
        "security": [],
        "description": "Step 2 of 2. Takes the token from the emailed link and creates the account with one Free key, shown once. One free key per verified email (Gmail dots and +tags count as the same address).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "token"
                ],
                "additionalProperties": false,
                "properties": {
                  "token": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Account and key created"
          },
          "400": {
            "description": "Invalid link",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Link already used, or the email already has a key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "Link expired",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts from this IP, email or account (Retry-After header)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/account": {
      "get": {
        "operationId": "account_get",
        "tags": [
          "Account"
        ],
        "summary": "Your account: plan, usage this month, keys",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Free (0 units).",
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/account/key/rotate": {
      "post": {
        "operationId": "account_key_rotate",
        "tags": [
          "Account"
        ],
        "summary": "Replace your key with a new one",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Issues a new secret for the same key id (the calling key unless key_id names another key of the account). The old secret stops working at once; usage and quota carry on. The new key is shown once.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [],
                "additionalProperties": false,
                "properties": {
                  "key_id": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "New key"
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Key not in this account",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts from this IP, email or account (Retry-After header)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/account/key/revoke": {
      "post": {
        "operationId": "account_key_revoke",
        "tags": [
          "Account"
        ],
        "summary": "Revoke a key",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "The key stops working at once. Get a new one with the lost-key flow (/v1/account/recover).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "confirm"
                ],
                "additionalProperties": false,
                "properties": {
                  "key_id": {
                    "type": "string"
                  },
                  "confirm": {
                    "type": "boolean",
                    "const": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Revoked"
          },
          "400": {
            "description": "confirm missing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts from this IP, email or account (Retry-After header)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/account/recover": {
      "post": {
        "operationId": "account_recover",
        "tags": [
          "Account"
        ],
        "summary": "Lost your key: email a link to get a new one",
        "security": [],
        "description": "Checks Turnstile and per-IP and per-email limits. The 202 answer is the same whether or not the address has an account. The link is valid 15 minutes.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "turnstile_token"
                ],
                "additionalProperties": false,
                "properties": {
                  "email": {
                    "type": "string",
                    "maxLength": 254
                  },
                  "turnstile_token": {
                    "type": "string",
                    "maxLength": 2048
                  },
                  "product": {
                    "type": "string",
                    "enum": [
                      "tariffs",
                      "food",
                      "cosmetics"
                    ],
                    "description": "The product site the request came from (the link points back to it; default: the product the account signed up on)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Sent if the account exists"
          },
          "400": {
            "description": "Invalid email or no Turnstile token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The human check failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts from this IP, email or account (Retry-After header)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/account/recover/confirm": {
      "post": {
        "operationId": "account_recover_confirm",
        "tags": [
          "Account"
        ],
        "summary": "Confirm the lost-key link and get a new key",
        "security": [],
        "description": "Replaces the account's key secret (or reissues a key you revoked). Keys revoked by us are not reissued. The new key is shown once.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "token"
                ],
                "additionalProperties": false,
                "properties": {
                  "token": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "New key"
          },
          "400": {
            "description": "Invalid link",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Key revoked by us or account suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Link already used",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "Link expired",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts from this IP, email or account (Retry-After header)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/usage": {
      "get": {
        "operationId": "usage_get",
        "tags": [
          "Account"
        ],
        "summary": "Your usage this month",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Units and calls per endpoint for the calling key, and per product: plan, units used and monthly units. Free (0 units).",
        "parameters": [
          {
            "name": "period",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/products": {
      "get": {
        "operationId": "products_list",
        "tags": [
          "Account"
        ],
        "summary": "Products, stages and plans with prices",
        "security": [],
        "description": "One account and one key work across products; each product has its own plan (a free tier, or a paid subscription) and its own monthly units. Waitlist products are not sold yet.",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/v1/billing/checkout": {
      "post": {
        "operationId": "billing_checkout",
        "tags": [
          "Account"
        ],
        "summary": "Start a subscription (Stripe Checkout)",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Creates a Stripe Checkout session for one product and plan and returns its URL. Payments are in the Stripe sandbox until going live is approved; with billing off the answer is 501 with what would be created.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "product",
                  "plan"
                ],
                "additionalProperties": false,
                "properties": {
                  "product": {
                    "type": "string",
                    "enum": [
                      "tariffs",
                      "food"
                    ]
                  },
                  "plan": {
                    "type": "string",
                    "enum": [
                      "developer",
                      "pro"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Checkout session created: data.url"
          },
          "400": {
            "description": "Unknown product or plan",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Product not sold yet",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Already subscribed: use the billing portal",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts from this IP, email or account (Retry-After header)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "501": {
            "description": "Billing is off",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/billing/portal": {
      "post": {
        "operationId": "billing_portal",
        "tags": [
          "Account"
        ],
        "summary": "Manage subscriptions (Stripe Customer Portal)",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Returns a Customer Portal URL to change plan, cancel (at period end), update the card or download invoices.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [],
                "additionalProperties": false,
                "properties": {
                  "product": {
                    "type": "string",
                    "enum": [
                      "tariffs",
                      "food"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Portal session created: data.url"
          },
          "404": {
            "description": "No subscription yet",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "501": {
            "description": "Billing is off",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/waitlist": {
      "post": {
        "operationId": "waitlist_join",
        "tags": [
          "Account"
        ],
        "summary": "Join a product's waitlist",
        "security": [],
        "description": "For products not sold yet. Same 202 answer whether or not the address is already on the list.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "product",
                  "email",
                  "consent"
                ],
                "additionalProperties": false,
                "properties": {
                  "product": {
                    "type": "string",
                    "enum": [
                      "tariffs",
                      "food",
                      "cosmetics"
                    ]
                  },
                  "email": {
                    "type": "string",
                    "maxLength": 254
                  },
                  "name": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "company": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "use_case": {
                    "type": "string",
                    "maxLength": 1000
                  },
                  "consent": {
                    "type": "boolean",
                    "const": true
                  },
                  "turnstile_token": {
                    "type": "string",
                    "maxLength": 2048
                  },
                  "attribution": {
                    "type": "object"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "On the waitlist"
          },
          "400": {
            "description": "Invalid email, product or missing consent",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The human check failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts from this IP, email or account (Retry-After header)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/client-errors": {
      "post": {
        "operationId": "client_errors",
        "tags": [
          "Account"
        ],
        "summary": "Report a browser error from one of our sites",
        "security": [],
        "description": "Used by the sites' error handler. Only the page path is kept (no query or fragment). Rate-limited per IP.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "message"
                ],
                "additionalProperties": false,
                "properties": {
                  "message": {
                    "type": "string",
                    "maxLength": 1000
                  },
                  "name": {
                    "type": "string"
                  },
                  "stack": {
                    "type": "string",
                    "maxLength": 8000
                  },
                  "page": {
                    "type": "string"
                  },
                  "source": {
                    "type": "string"
                  },
                  "product": {
                    "type": "string"
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "error",
                      "unhandledrejection"
                    ]
                  },
                  "release": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted"
          },
          "400": {
            "description": "No message",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts from this IP, email or account (Retry-After header)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/mcp": {
      "post": {
        "operationId": "mcp",
        "tags": [
          "MCP"
        ],
        "summary": "MCP server (Streamable HTTP, JSON-RPC 2.0)",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Model Context Protocol endpoint for AI agents. Methods: initialize, tools/list, tools/call, ping. Every REST operation above is a tool with the same input schema and the same metering.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "jsonrpc": "2.0",
                "id": 1,
                "method": "tools/list"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC response"
          },
          "202": {
            "description": "Notification accepted"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key"
      }
    },
    "schemas": {
      "Meta": {
        "type": "object",
        "required": [
          "request_id",
          "dataset",
          "dataset_version",
          "data_status",
          "sources",
          "disclaimer"
        ],
        "properties": {
          "request_id": {
            "type": "string"
          },
          "api_version": {
            "type": "string"
          },
          "dataset": {
            "type": "string",
            "enum": [
              "food",
              "tariffs",
              "cosmetics"
            ]
          },
          "dataset_version": {
            "type": "string"
          },
          "dataset_built_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "data_status": {
            "type": "string",
            "enum": [
              "released"
            ],
            "description": "released = served from the dataset's released build (dist/data.sqlite)"
          },
          "data_status_note": {
            "type": "string"
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "licence": {
                  "type": "string"
                },
                "commercial_redistribution_ok": {
                  "type": "boolean"
                }
              }
            }
          },
          "provenance_note": {
            "type": "string"
          },
          "disclaimer": {
            "type": "string"
          },
          "usage": {
            "type": "object",
            "properties": {
              "plan": {
                "type": "string"
              },
              "period": {
                "type": "string"
              },
              "units_charged": {
                "type": "integer"
              },
              "units_used": {
                "type": "integer"
              },
              "monthly_units": {
                "type": "integer"
              }
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              },
              "details": {}
            }
          },
          "meta": {
            "type": "object",
            "properties": {
              "request_id": {
                "type": "string"
              }
            }
          }
        }
      }
    }
  }
}
