{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/tinyplates-dev/json-schema/tinyplates-dev-pantry-recipe-schema.json",
  "title": "PantryRecipe",
  "description": "A recipe together with what your pantry covers and what it does not. Ingredient names are the recipe's own, in its own language.",
  "x-generated": "2026-10-31",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/tinyplates-dev-openapi.json#/components/schemas/PantryRecipe",
  "type": "object",
  "required": [
    "id",
    "instructions",
    "language",
    "media",
    "times",
    "title",
    "categories",
    "courses",
    "createdAt",
    "cuisines",
    "groups",
    "ingredients",
    "tags",
    "text",
    "updatedAt",
    "url",
    "assumedIngredients",
    "coverage",
    "matchedIngredients",
    "missingIngredients"
  ],
  "properties": {
    "id": {
      "type": "string"
    },
    "author": {
      "$ref": "#/$defs/Author"
    },
    "description": {
      "$ref": "#/$defs/NonEmptyTrimmedString"
    },
    "dietary": {
      "$ref": "#/$defs/Dietary"
    },
    "instructions": {
      "type": "array",
      "minItems": 1,
      "items": {
        "$ref": "#/$defs/RecipeInstruction"
      }
    },
    "language": {
      "$ref": "#/$defs/Language"
    },
    "media": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/RecipeMedia"
      }
    },
    "notes": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/NonEmptyTrimmedString"
      }
    },
    "nutrition": {
      "$ref": "#/$defs/Nutrition"
    },
    "rating": {
      "$ref": "#/$defs/Rating"
    },
    "servings": {
      "$ref": "#/$defs/Servings"
    },
    "times": {
      "$ref": "#/$defs/RecipeTimes"
    },
    "title": {
      "$ref": "#/$defs/NonEmptyTrimmedString"
    },
    "allergens": {
      "$ref": "#/$defs/RecipeAllergens"
    },
    "categories": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "courses": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "createdAt": {
      "$ref": "#/$defs/Date"
    },
    "cuisines": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "equipment": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "groups": {
      "$ref": "#/$defs/RecipeGroups"
    },
    "ingredients": {
      "type": "array",
      "minItems": 1,
      "items": {
        "$ref": "#/$defs/RecipeIngredient"
      }
    },
    "tags": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "techniques": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "text": {
      "$ref": "#/$defs/RecipeText"
    },
    "updatedAt": {
      "$ref": "#/$defs/Date"
    },
    "url": {
      "type": "string"
    },
    "assumedIngredients": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "The staples this recipe uses — salt, pepper, water, oil, sugar, flour — taken as already in your kitchen, and so counted neither in the coverage nor as missing. Empty when \"staples=false\"."
    },
    "coverage": {
      "type": "number",
      "description": "The share of this recipe's ingredients your pantry covers, from 0 to 1, rounded to two decimals. Higher comes first."
    },
    "matchedIngredients": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "The recipe ingredients your pantry covers, as the recipe names them."
    },
    "missingIngredients": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "The recipe ingredients your pantry does not cover, as the recipe names them."
    }
  },
  "additionalProperties": false,
  "$defs": {
    "Author": {
      "type": "object",
      "required": [
        "name"
      ],
      "properties": {
        "name": {
          "$ref": "#/$defs/NonEmptyTrimmedString"
        },
        "url": {
          "$ref": "#/$defs/Url"
        }
      },
      "additionalProperties": false
    },
    "Date": {
      "type": "string",
      "description": "a string to be decoded into a Date"
    },
    "Dietary": {
      "type": "object",
      "required": [],
      "properties": {
        "dairyFree": {
          "type": "boolean"
        },
        "glutenFree": {
          "type": "boolean"
        },
        "pescatarian": {
          "type": "boolean"
        },
        "vegan": {
          "type": "boolean"
        },
        "vegetarian": {
          "type": "boolean"
        }
      },
      "additionalProperties": false,
      "description": "Diet flags declared by the site or inferred from the required ingredients. A declared flag wins; a missing flag is unknown, and false is a known no."
    },
    "Duration": {
      "type": "object",
      "required": [
        "seconds"
      ],
      "properties": {
        "seconds": {
          "$ref": "#/$defs/NonNegativeInt"
        }
      },
      "additionalProperties": false
    },
    "ImageMedia": {
      "type": "object",
      "required": [
        "id",
        "role",
        "type",
        "url"
      ],
      "properties": {
        "alt": {
          "$ref": "#/$defs/NonEmptyTrimmedString"
        },
        "caption": {
          "$ref": "#/$defs/NonEmptyTrimmedString"
        },
        "id": {
          "$ref": "#/$defs/Trimmed",
          "description": "a non empty string",
          "title": "nonEmptyString",
          "minLength": 1
        },
        "license": {
          "$ref": "#/$defs/NonEmptyTrimmedString"
        },
        "sourceUrl": {
          "$ref": "#/$defs/Url"
        },
        "step": {
          "type": "integer",
          "description": "a positive number",
          "title": "positive",
          "exclusiveMinimum": 0
        },
        "height": {
          "type": "integer",
          "description": "a positive number",
          "title": "positive",
          "exclusiveMinimum": 0
        },
        "role": {
          "type": "string",
          "enum": [
            "gallery",
            "hero",
            "step",
            "thumbnail"
          ]
        },
        "type": {
          "type": "string",
          "enum": [
            "image"
          ]
        },
        "url": {
          "$ref": "#/$defs/Url"
        },
        "variants": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/ImageVariant"
          }
        },
        "width": {
          "type": "integer",
          "description": "a positive number",
          "title": "positive",
          "exclusiveMinimum": 0
        }
      },
      "additionalProperties": false
    },
    "ImageVariant": {
      "type": "object",
      "required": [
        "url"
      ],
      "properties": {
        "height": {
          "type": "integer",
          "description": "a positive number",
          "title": "positive",
          "exclusiveMinimum": 0
        },
        "url": {
          "$ref": "#/$defs/Url"
        },
        "width": {
          "type": "integer",
          "description": "a positive number",
          "title": "positive",
          "exclusiveMinimum": 0
        }
      },
      "additionalProperties": false
    },
    "Language": {
      "type": "string",
      "description": "a string matching the pattern ^[a-z]{2}$",
      "pattern": "^[a-z]{2}$"
    },
    "NonEmptyTrimmedString": {
      "type": "string",
      "description": "a non empty string",
      "title": "nonEmptyString",
      "pattern": "^\\S[\\s\\S]*\\S$|^\\S$|^$",
      "minLength": 1
    },
    "NonNegative": {
      "type": "number",
      "description": "a non-negative number",
      "title": "nonNegative",
      "minimum": 0
    },
    "NonNegativeInt": {
      "type": "integer",
      "description": "an integer",
      "title": "int",
      "minimum": 0
    },
    "NutrientUnit": {
      "type": "string",
      "enum": [
        "IU",
        "g",
        "kcal",
        "mg",
        "µg"
      ]
    },
    "NutrientValue": {
      "type": "object",
      "required": [
        "unit",
        "value"
      ],
      "properties": {
        "unit": {
          "$ref": "#/$defs/NutrientUnit"
        },
        "value": {
          "$ref": "#/$defs/NonNegative"
        }
      },
      "additionalProperties": false
    },
    "Nutrition": {
      "type": "object",
      "required": [
        "basis",
        "source"
      ],
      "properties": {
        "basis": {
          "type": "object",
          "required": [
            "type"
          ],
          "properties": {
            "description": {
              "$ref": "#/$defs/NonEmptyTrimmedString"
            },
            "servings": {
              "$ref": "#/$defs/Positive"
            },
            "type": {
              "type": "string",
              "enum": [
                "100g",
                "recipe",
                "serving"
              ]
            }
          },
          "additionalProperties": false
        },
        "calories": {
          "$ref": "#/$defs/NutrientValue"
        },
        "carbohydrates": {
          "$ref": "#/$defs/NutrientValue"
        },
        "cholesterol": {
          "$ref": "#/$defs/NutrientValue"
        },
        "fat": {
          "$ref": "#/$defs/NutrientValue"
        },
        "fiber": {
          "$ref": "#/$defs/NutrientValue"
        },
        "micronutrients": {
          "type": "object",
          "required": [],
          "properties": {},
          "patternProperties": {
            "": {
              "$ref": "#/$defs/NutrientValue"
            }
          },
          "propertyNames": {
            "$ref": "#/$defs/Slug"
          }
        },
        "protein": {
          "$ref": "#/$defs/NutrientValue"
        },
        "saturatedFat": {
          "$ref": "#/$defs/NutrientValue"
        },
        "sodium": {
          "$ref": "#/$defs/NutrientValue"
        },
        "source": {
          "type": "string",
          "enum": [
            "calculated",
            "provided"
          ]
        },
        "sugar": {
          "$ref": "#/$defs/NutrientValue"
        }
      },
      "additionalProperties": false
    },
    "Positive": {
      "type": "number",
      "description": "a positive number",
      "title": "positive",
      "exclusiveMinimum": 0
    },
    "Quantity": {
      "type": "object",
      "required": [
        "value"
      ],
      "properties": {
        "max": {
          "$ref": "#/$defs/NonNegative"
        },
        "unit": {
          "$ref": "#/$defs/Unit"
        },
        "value": {
          "$ref": "#/$defs/NonNegative"
        }
      },
      "additionalProperties": false
    },
    "Rating": {
      "type": "object",
      "required": [
        "count",
        "value"
      ],
      "properties": {
        "best": {
          "$ref": "#/$defs/Positive"
        },
        "count": {
          "$ref": "#/$defs/NonNegativeInt"
        },
        "reviewCount": {
          "$ref": "#/$defs/NonNegativeInt"
        },
        "value": {
          "$ref": "#/$defs/NonNegative"
        },
        "worst": {
          "$ref": "#/$defs/NonNegative"
        }
      },
      "additionalProperties": false
    },
    "RecipeAllergens": {
      "type": "object",
      "required": [
        "celery",
        "crustaceans",
        "eggs",
        "fish",
        "gluten-cereals",
        "lupin",
        "milk",
        "molluscs",
        "mustard",
        "nuts-eu",
        "peanuts",
        "sesame",
        "soy",
        "sulphites",
        "tree-nuts-us",
        "wheat",
        "disclaimer"
      ],
      "properties": {
        "celery": {
          "type": "boolean",
          "description": "Whether the recipe contains this regulated allergen."
        },
        "crustaceans": {
          "type": "boolean",
          "description": "Whether the recipe contains this regulated allergen."
        },
        "eggs": {
          "type": "boolean",
          "description": "Whether the recipe contains this regulated allergen."
        },
        "fish": {
          "type": "boolean",
          "description": "Whether the recipe contains this regulated allergen."
        },
        "gluten-cereals": {
          "type": "boolean",
          "description": "Whether the recipe contains this regulated allergen."
        },
        "lupin": {
          "type": "boolean",
          "description": "Whether the recipe contains this regulated allergen."
        },
        "milk": {
          "type": "boolean",
          "description": "Whether the recipe contains this regulated allergen."
        },
        "molluscs": {
          "type": "boolean",
          "description": "Whether the recipe contains this regulated allergen."
        },
        "mustard": {
          "type": "boolean",
          "description": "Whether the recipe contains this regulated allergen."
        },
        "nuts-eu": {
          "type": "boolean",
          "description": "Whether the recipe contains this regulated allergen."
        },
        "peanuts": {
          "type": "boolean",
          "description": "Whether the recipe contains this regulated allergen."
        },
        "sesame": {
          "type": "boolean",
          "description": "Whether the recipe contains this regulated allergen."
        },
        "soy": {
          "type": "boolean",
          "description": "Whether the recipe contains this regulated allergen."
        },
        "sulphites": {
          "type": "boolean",
          "description": "Whether the recipe contains this regulated allergen."
        },
        "tree-nuts-us": {
          "type": "boolean",
          "description": "Whether the recipe contains this regulated allergen."
        },
        "wheat": {
          "type": "boolean",
          "description": "Whether the recipe contains this regulated allergen."
        },
        "disclaimer": {
          "type": "string",
          "enum": [
            "Informational only; not medical advice. Verify ingredients and product labels before serving someone with an allergy."
          ]
        }
      },
      "additionalProperties": false
    },
    "RecipeGroups": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "The distinct `group` values of the ingredients and then the steps, in the order they first appear. Empty when the recipe has no sections."
    },
    "RecipeIngredient": {
      "type": "object",
      "required": [
        "display",
        "name",
        "optional",
        "original"
      ],
      "properties": {
        "display": {
          "$ref": "#/$defs/NonEmptyTrimmedString"
        },
        "group": {
          "$ref": "#/$defs/NonEmptyTrimmedString"
        },
        "localName": {
          "$ref": "#/$defs/NonEmptyTrimmedString"
        },
        "localPreparation": {
          "$ref": "#/$defs/NonEmptyTrimmedString"
        },
        "ingredientId": {
          "type": "string",
          "description": "a string matching the pattern ^[a-z0-9]+(-[a-z0-9]+)*$",
          "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$"
        },
        "name": {
          "$ref": "#/$defs/NonEmptyTrimmedString"
        },
        "note": {
          "$ref": "#/$defs/NonEmptyTrimmedString"
        },
        "optional": {
          "type": "boolean"
        },
        "original": {
          "$ref": "#/$defs/NonEmptyTrimmedString"
        },
        "preparation": {
          "$ref": "#/$defs/NonEmptyTrimmedString"
        },
        "quantity": {
          "$ref": "#/$defs/Quantity"
        },
        "size": {
          "$ref": "#/$defs/NonEmptyTrimmedString"
        }
      },
      "additionalProperties": false
    },
    "RecipeInstruction": {
      "type": "object",
      "required": [
        "step",
        "text"
      ],
      "properties": {
        "duration": {
          "$ref": "#/$defs/Duration"
        },
        "equipment": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/Slug"
          }
        },
        "group": {
          "$ref": "#/$defs/NonEmptyTrimmedString"
        },
        "ingredients": {
          "type": "array",
          "items": {
            "type": "string",
            "description": "a string matching the pattern ^[a-z0-9]+(-[a-z0-9]+)*$",
            "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$"
          }
        },
        "step": {
          "type": "integer",
          "description": "a positive number",
          "title": "positive",
          "exclusiveMinimum": 0
        },
        "techniques": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/Slug"
          }
        },
        "temperature": {
          "$ref": "#/$defs/Temperature"
        },
        "text": {
          "$ref": "#/$defs/NonEmptyTrimmedString"
        }
      },
      "additionalProperties": false
    },
    "RecipeMedia": {
      "anyOf": [
        {
          "$ref": "#/$defs/ImageMedia"
        },
        {
          "$ref": "#/$defs/VideoMedia"
        }
      ]
    },
    "RecipeText": {
      "type": "object",
      "required": [
        "ingredients",
        "instructions"
      ],
      "properties": {
        "ingredients": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "instructions": {
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      },
      "additionalProperties": false,
      "description": "The recipe as lines of text: each ingredient's `display` line and each step's `text`, in order. A convenience for rendering; everything here is also in the structured fields."
    },
    "RecipeTimes": {
      "type": "object",
      "required": [],
      "properties": {
        "cook": {
          "$ref": "#/$defs/Duration"
        },
        "inactive": {
          "$ref": "#/$defs/Duration"
        },
        "prep": {
          "$ref": "#/$defs/Duration"
        },
        "total": {
          "$ref": "#/$defs/Duration"
        }
      },
      "additionalProperties": false
    },
    "Servings": {
      "type": "object",
      "required": [
        "original"
      ],
      "properties": {
        "max": {
          "$ref": "#/$defs/Positive"
        },
        "original": {
          "$ref": "#/$defs/NonEmptyTrimmedString"
        },
        "quantity": {
          "$ref": "#/$defs/Positive"
        },
        "unit": {
          "$ref": "#/$defs/NonEmptyTrimmedString"
        }
      },
      "additionalProperties": false
    },
    "Slug": {
      "type": "string",
      "description": "a string matching the pattern ^[a-z0-9]+(-[a-z0-9]+)*$",
      "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$"
    },
    "Temperature": {
      "type": "object",
      "required": [
        "unit",
        "value"
      ],
      "properties": {
        "unit": {
          "type": "string",
          "enum": [
            "C",
            "F"
          ]
        },
        "value": {
          "type": "number"
        }
      },
      "additionalProperties": false
    },
    "Trimmed": {
      "type": "string",
      "description": "a string with no leading or trailing whitespace",
      "title": "trimmed",
      "pattern": "^\\S[\\s\\S]*\\S$|^\\S$|^$"
    },
    "Unit": {
      "type": "string",
      "enum": [
        "g",
        "kg",
        "lb",
        "oz",
        "cl",
        "cup",
        "dl",
        "fl-oz",
        "gallon",
        "l",
        "ml",
        "pint",
        "quart",
        "tbsp",
        "tsp",
        "bag",
        "bottle",
        "box",
        "bunch",
        "can",
        "clove",
        "cube",
        "dash",
        "drop",
        "ear",
        "handful",
        "head",
        "jar",
        "knob",
        "leaf",
        "package",
        "packet",
        "piece",
        "pinch",
        "portion",
        "pot",
        "scoop",
        "sheet",
        "shot",
        "slice",
        "sprig",
        "stalk",
        "stick",
        "tub",
        "cm",
        "inch"
      ]
    },
    "Url": {
      "type": "string",
      "description": "a string matching the pattern ^https?:\\/\\/\\S+$",
      "pattern": "^https?:\\/\\/\\S+$"
    },
    "VideoMedia": {
      "type": "object",
      "required": [
        "id",
        "role",
        "type",
        "url"
      ],
      "properties": {
        "alt": {
          "$ref": "#/$defs/NonEmptyTrimmedString"
        },
        "caption": {
          "$ref": "#/$defs/NonEmptyTrimmedString"
        },
        "id": {
          "$ref": "#/$defs/Trimmed",
          "description": "a non empty string",
          "title": "nonEmptyString",
          "minLength": 1
        },
        "license": {
          "$ref": "#/$defs/NonEmptyTrimmedString"
        },
        "sourceUrl": {
          "$ref": "#/$defs/Url"
        },
        "step": {
          "type": "integer",
          "description": "a positive number",
          "title": "positive",
          "exclusiveMinimum": 0
        },
        "duration": {
          "$ref": "#/$defs/Duration"
        },
        "embedUrl": {
          "$ref": "#/$defs/Url"
        },
        "provider": {
          "$ref": "#/$defs/NonEmptyTrimmedString"
        },
        "role": {
          "type": "string",
          "enum": [
            "gallery",
            "step"
          ]
        },
        "thumbnailUrl": {
          "$ref": "#/$defs/Url"
        },
        "type": {
          "type": "string",
          "enum": [
            "video"
          ]
        },
        "url": {
          "$ref": "#/$defs/Url"
        },
        "videoId": {
          "$ref": "#/$defs/NonEmptyTrimmedString"
        }
      },
      "additionalProperties": false
    }
  }
}