{
    "openapi": "3.1.0",
    "info": {
        "title": "The Saucery Developer API",
        "version": "1.0.0",
        "description": "The Saucery Developer API lets you manage shopping lists programmatically.\n\n## Authentication\n\nAll requests require a Bearer token. Create an API key in the app under **Settings \u2192 API Keys** (family owner only).\n\n```\nAuthorization: Bearer sc_key_xxxxxxxxxxxxxxxx\n```\n\n## Scopes\n\nKeys can be scoped to limit access:\n\n| Scope | Description |\n|-------|-------------|\n| `shopping-list:read` | Read shopping list and items |\n| `shopping-list:write` | Add, check, and remove shopping list items |"
    },
    "servers": [
        {
            "url": "https:\/\/thesaucery.nz\/api\/v1"
        }
    ],
    "security": [
        {
            "http": []
        }
    ],
    "paths": {
        "\/meal-plan": {
            "get": {
                "operationId": "mealPlan.show",
                "summary": "Get the meal plan for the current (or specified) week",
                "tags": [
                    "MealPlan"
                ],
                "responses": {
                    "200": {
                        "description": "",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#\/components\/schemas\/MealPlanResource"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ]
                                }
                            }
                        }
                    }
                }
            }
        },
        "\/shopping-list": {
            "get": {
                "operationId": "shoppingList.show",
                "summary": "Get the current shopping list",
                "tags": [
                    "ShoppingList"
                ],
                "responses": {
                    "200": {
                        "description": "",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#\/components\/schemas\/ShoppingListResource"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ]
                                }
                            }
                        }
                    }
                }
            }
        },
        "\/shopping-list\/items\/{item}": {
            "get": {
                "operationId": "shoppingList.showItem",
                "summary": "Get a single shopping list item",
                "tags": [
                    "ShoppingList"
                ],
                "parameters": [
                    {
                        "name": "item",
                        "in": "path",
                        "required": true,
                        "description": "The item UUID",
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#\/components\/schemas\/ShoppingListItemResource"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ]
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "An error",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "message": {
                                            "type": "string",
                                            "description": "Error overview.",
                                            "example": ""
                                        }
                                    },
                                    "required": [
                                        "message"
                                    ]
                                }
                            }
                        }
                    },
                    "404": {
                        "$ref": "#\/components\/responses\/ModelNotFoundException"
                    }
                }
            },
            "delete": {
                "operationId": "shoppingList.removeItem",
                "summary": "Remove an item from the shopping list",
                "tags": [
                    "ShoppingList"
                ],
                "parameters": [
                    {
                        "name": "item",
                        "in": "path",
                        "required": true,
                        "description": "The item UUID",
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "message": {
                                            "type": "string",
                                            "const": "Item removed"
                                        }
                                    },
                                    "required": [
                                        "message"
                                    ]
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "An error",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "message": {
                                            "type": "string",
                                            "description": "Error overview.",
                                            "example": ""
                                        }
                                    },
                                    "required": [
                                        "message"
                                    ]
                                }
                            }
                        }
                    },
                    "404": {
                        "$ref": "#\/components\/responses\/ModelNotFoundException"
                    }
                }
            }
        },
        "\/shopping-list\/items": {
            "post": {
                "operationId": "shoppingList.addItem",
                "summary": "Add an item to the shopping list",
                "tags": [
                    "ShoppingList"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "$ref": "#\/components\/schemas\/AddShoppingListItemRequest"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#\/components\/schemas\/ShoppingListItemResource"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ]
                                }
                            }
                        }
                    },
                    "200": {
                        "description": "",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#\/components\/schemas\/ShoppingListItemResource"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ]
                                }
                            }
                        }
                    },
                    "422": {
                        "$ref": "#\/components\/responses\/ValidationException"
                    }
                }
            }
        },
        "\/shopping-list\/items\/{item}\/check": {
            "post": {
                "operationId": "shoppingList.checkItem",
                "summary": "Toggle an item's checked status",
                "tags": [
                    "ShoppingList"
                ],
                "parameters": [
                    {
                        "name": "item",
                        "in": "path",
                        "required": true,
                        "description": "The item UUID",
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#\/components\/schemas\/ShoppingListItemResource"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ]
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "An error",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "message": {
                                            "type": "string",
                                            "description": "Error overview.",
                                            "example": ""
                                        }
                                    },
                                    "required": [
                                        "message"
                                    ]
                                }
                            }
                        }
                    },
                    "404": {
                        "$ref": "#\/components\/responses\/ModelNotFoundException"
                    }
                }
            }
        }
    },
    "components": {
        "securitySchemes": {
            "http": {
                "type": "http",
                "scheme": "bearer"
            }
        },
        "schemas": {
            "AddShoppingListItemRequest": {
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string",
                        "maxLength": 255
                    },
                    "quantity": {
                        "type": [
                            "number",
                            "null"
                        ],
                        "minimum": 0
                    },
                    "unit": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 50
                    },
                    "group": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 100
                    }
                },
                "required": [
                    "name"
                ],
                "title": "AddShoppingListItemRequest"
            },
            "FamilyCookResource": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "string",
                        "description": "API: Send this as cook_id to make them a night's cook, or to DELETE \/family\/cooks\/{id} to forget the name."
                    },
                    "name": {
                        "type": "string"
                    },
                    "pending": {
                        "type": "boolean",
                        "description": "API: True while they are invited and the link can still be used. Once they join they become a member and leave this list."
                    }
                },
                "required": [
                    "id",
                    "name",
                    "pending"
                ],
                "title": "FamilyCookResource"
            },
            "FamilyMemberResource": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "string"
                    },
                    "name": {
                        "type": "string"
                    },
                    "email": {
                        "type": "string"
                    },
                    "avatar": {
                        "type": "string"
                    },
                    "role": {
                        "type": "string"
                    }
                },
                "required": [
                    "id",
                    "name",
                    "email",
                    "avatar",
                    "role"
                ],
                "title": "FamilyMemberResource"
            },
            "FamilyResource": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "string"
                    },
                    "name": {
                        "type": "string"
                    },
                    "avatar": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "start_day": {
                        "type": "integer"
                    },
                    "default_servings": {
                        "type": "integer"
                    },
                    "dietary_restrictions": {
                        "type": [
                            "array",
                            "null"
                        ],
                        "description": "API: Diets the whole family keeps, such as vegetarian or gluten-free. Null when none are set.",
                        "items": {}
                    },
                    "preferred_cuisines": {
                        "type": [
                            "array",
                            "null"
                        ],
                        "description": "API: Cuisines the family likes, used when suggesting new dinner ideas. Null when none are set.",
                        "items": {}
                    },
                    "house_rules": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "API: The family's standing rules in their own words, such as \"no spicy food, we eat rice or potatoes with most dinners\". Read by Fill my week's new ideas and by the assistant. Null when none are written."
                    },
                    "avoid_words": {
                        "type": "array",
                        "description": "API: Words to keep off the plan, lower case. Fill my week suggests a recipe whose title, cuisine, tags or ingredients mention one only when nothing else fits. Always present; empty when none.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "quick_weekdays": {
                        "type": "array",
                        "description": "API: The weekdays that are always quick nights, 0 = Sunday to 6 = Saturday like start_day. Fill my week starts these nights with Quick on. Always present; empty when none.",
                        "items": {
                            "type": "integer"
                        }
                    },
                    "subscription_tier": {
                        "type": "string"
                    },
                    "is_entitled": {
                        "type": "boolean",
                        "description": "Authoritative paid-access flag: trial OR Stripe OR Apple\/Google OR\nadmin comp. Clients gate features on this, not on the tier.\nAPI: Always present; never null."
                    },
                    "entitlement_source": {
                        "type": "string"
                    },
                    "entitlement_expires_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    },
                    "on_trial": {
                        "type": "boolean",
                        "description": "API: True only while the trial is the ACTIVE entitlement rail \u2014\na paid subscriber inside their original trial window is not on\ntrial."
                    },
                    "trial_ends_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    },
                    "trial_days_remaining": {
                        "type": "integer"
                    },
                    "can_manage_billing": {
                        "type": "boolean",
                        "description": "Only an admin may start or change the subscription; every member\nis entitled (dig #3628). Clients hide purchase on this rather\nthan on role names.\nAPI: True when the signed-in person may buy or change the family's subscription (a family admin)."
                    },
                    "billing_managers": {
                        "type": "array",
                        "description": "API: The names of the people who look after the plan (the family's admins, longest-standing first), for \"Losana looks after the plan.\"",
                        "items": {}
                    },
                    "recipe_limit": {
                        "type": "integer"
                    },
                    "recipe_count": {
                        "type": "integer",
                        "minimum": 0
                    },
                    "min_special_savings": {
                        "type": "number"
                    },
                    "price_provider": {
                        "$ref": "#\/components\/schemas\/PriceProviderResource"
                    },
                    "members": {
                        "type": "array",
                        "items": {
                            "$ref": "#\/components\/schemas\/FamilyMemberResource"
                        }
                    },
                    "cooks": {
                        "type": "array",
                        "description": "Read alongside the members, wherever they are given.\nAPI: People who can be named as a night's cook without being members: invited people who haven't joined yet, and names typed in before, A to Z.",
                        "items": {
                            "$ref": "#\/components\/schemas\/FamilyCookResource"
                        }
                    },
                    "created_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    }
                },
                "required": [
                    "id",
                    "name",
                    "avatar",
                    "start_day",
                    "default_servings",
                    "dietary_restrictions",
                    "preferred_cuisines",
                    "house_rules",
                    "avoid_words",
                    "quick_weekdays",
                    "subscription_tier",
                    "is_entitled",
                    "entitlement_source",
                    "entitlement_expires_at",
                    "on_trial",
                    "trial_ends_at",
                    "trial_days_remaining",
                    "can_manage_billing",
                    "billing_managers",
                    "recipe_limit",
                    "min_special_savings",
                    "created_at"
                ],
                "title": "FamilyResource"
            },
            "IngredientResource": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "string"
                    },
                    "name": {
                        "type": "string"
                    },
                    "note": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "quantity": {
                        "type": [
                            "number",
                            "null"
                        ]
                    },
                    "unit": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "unit_display_one": {
                        "type": "string",
                        "description": "Display helpers: clients pick `one`\/`other` by the locally\nserving-scaled quantity and pluralise the name when asked."
                    },
                    "unit_display_other": {
                        "type": "string"
                    },
                    "pluralize_name": {
                        "type": "string"
                    },
                    "name_one": {
                        "type": "string",
                        "description": "Clients older than these fields append \"s\" to `name` themselves.\nAPI: The name to show at a quantity of one or less, inflected on its noun: \"large garlic clove\". With no quantity, show `name`."
                    },
                    "name_other": {
                        "type": "string",
                        "description": "API: The name to show above a quantity of one: \"large garlic cloves\", \"cloves of garlic\"."
                    },
                    "group": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "order": {
                        "type": "integer"
                    }
                },
                "required": [
                    "id",
                    "name",
                    "note",
                    "quantity",
                    "unit",
                    "unit_display_one",
                    "unit_display_other",
                    "pluralize_name",
                    "name_one",
                    "name_other",
                    "group",
                    "order"
                ],
                "title": "IngredientResource"
            },
            "LabItemResource": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "string"
                    },
                    "slug": {
                        "type": "string"
                    },
                    "name": {
                        "type": "string"
                    },
                    "category": {
                        "type": "string"
                    },
                    "short_description": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "badge_url": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "has_badge_image": {
                        "type": "boolean"
                    },
                    "long_description": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "context": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "earned": {
                        "type": "boolean"
                    },
                    "earned_at": {
                        "type": "string"
                    },
                    "times_earned": {
                        "type": "integer"
                    }
                },
                "required": [
                    "id",
                    "slug",
                    "name",
                    "category",
                    "short_description",
                    "badge_url",
                    "has_badge_image",
                    "long_description",
                    "context",
                    "earned",
                    "earned_at",
                    "times_earned"
                ],
                "title": "LabItemResource"
            },
            "MealPlanEntryResource": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "string"
                    },
                    "date": {
                        "type": "string"
                    },
                    "meal_type": {
                        "type": "string"
                    },
                    "recipe": {
                        "$ref": "#\/components\/schemas\/RecipeResource"
                    },
                    "recipe_id": {
                        "type": "string"
                    },
                    "recipe_link_source": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "API: 'user' if someone picked the recipe, 'auto' if we inferred it\nfrom the label they typed. Clients can use it to offer an \"not this\n one?\" escape hatch on a link they didn't ask for; the label stays\nthe display string either way."
                    },
                    "label": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "servings": {
                        "type": [
                            "integer",
                            "null"
                        ]
                    },
                    "postponed_from_entry_id": {
                        "type": "string",
                        "description": "API: Kept for clients shipped before the day a postponed meal\nleft was freed. Always null now \u2014 read postponed_from_date."
                    },
                    "postponed_from_date": {
                        "type": "string",
                        "description": "API: The day this meal was first planned on, if it was postponed\nfrom there. Later moves keep it, and it clears when the meal goes\nback to that day or a different meal takes its place. Its\ningredients were bought for the original week, so it is\ndeliberately absent from this week's shopping list."
                    },
                    "ingredients_already_purchased": {
                        "type": "boolean"
                    },
                    "cooked_at": {
                        "type": "string"
                    },
                    "cook": {
                        "description": "API: Who is cooking this meal, or null when nobody has been named.\nIt moves with the meal: a move, swap or postpone takes it along,\nand a different meal on the same night keeps it. Set it with\nPUT \/meal-plan\/entry\/{id}\/cook. `user_id` is the member's user id\n(equal to `id`) when the cook is in the family, or null for\nsomeone without the app, whose `id` is one of the family's\n`cooks`. `pending` is true while that person is invited and\nhas not joined.",
                        "anyOf": [
                            {
                                "type": "null"
                            },
                            {
                                "type": "object",
                                "properties": {
                                    "id": {
                                        "type": "string"
                                    },
                                    "name": {
                                        "type": "string"
                                    },
                                    "user_id": {
                                        "type": "null"
                                    },
                                    "pending": {
                                        "type": "boolean"
                                    }
                                },
                                "required": [
                                    "id",
                                    "name",
                                    "user_id",
                                    "pending"
                                ]
                            },
                            {
                                "type": "object",
                                "properties": {
                                    "id": {
                                        "type": "string"
                                    },
                                    "name": {
                                        "type": "string"
                                    },
                                    "user_id": {
                                        "type": "string"
                                    },
                                    "pending": {
                                        "type": "boolean"
                                    }
                                },
                                "required": [
                                    "id",
                                    "name",
                                    "user_id",
                                    "pending"
                                ]
                            }
                        ]
                    },
                    "cook_user_id": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "API: The cook's user id (the same id as the family's members), or null, including when the cook is someone without the app."
                    }
                },
                "required": [
                    "id",
                    "date",
                    "meal_type",
                    "recipe_id",
                    "recipe_link_source",
                    "label",
                    "servings",
                    "postponed_from_entry_id",
                    "postponed_from_date",
                    "ingredients_already_purchased",
                    "cooked_at",
                    "cook",
                    "cook_user_id"
                ],
                "title": "MealPlanEntryResource"
            },
            "MealPlanResource": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "string"
                    },
                    "family_id": {
                        "anyOf": [
                            {
                                "type": "string"
                            },
                            {
                                "type": "integer"
                            }
                        ]
                    },
                    "week_start_date": {
                        "type": "string"
                    },
                    "entries": {
                        "type": "array",
                        "items": {
                            "$ref": "#\/components\/schemas\/MealPlanEntryResource"
                        }
                    },
                    "created_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    },
                    "updated_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    }
                },
                "required": [
                    "id",
                    "family_id",
                    "week_start_date",
                    "created_at",
                    "updated_at"
                ],
                "title": "MealPlanResource"
            },
            "PriceProviderResource": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "string"
                    },
                    "identifier": {
                        "type": "string"
                    },
                    "name": {
                        "type": "string"
                    },
                    "display_name": {
                        "type": "string",
                        "description": "API: The shop as a person names it, chain first: \"Pak'nSave Mill Street\". Show this wherever a chosen store is named, with the locality after it. Always present; falls back to name."
                    },
                    "region": {
                        "type": "string",
                        "description": "Two shipped clients decode this as a non-optional string, so it is\ncoerced rather than passed through: a row with no region would\nfail to decode on both of them.\nAPI: Always present; never null."
                    },
                    "retailer": {
                        "type": [
                            "object",
                            "null"
                        ],
                        "description": "API: The chain this store belongs to. Null on a legacy row that predates per-store providers.",
                        "properties": {
                            "slug": {
                                "type": [
                                    "string",
                                    "null"
                                ]
                            },
                            "name": {
                                "type": [
                                    "string",
                                    "null"
                                ]
                            }
                        },
                        "required": [
                            "slug",
                            "name"
                        ]
                    },
                    "store": {
                        "type": [
                            "object",
                            "null"
                        ],
                        "description": "API: The individual shop. Null for a retailer that publishes one national price.",
                        "properties": {
                            "slug": {
                                "type": [
                                    "string",
                                    "null"
                                ]
                            },
                            "name": {
                                "type": [
                                    "string",
                                    "null"
                                ]
                            },
                            "locality": {
                                "type": [
                                    "string",
                                    "null"
                                ]
                            },
                            "latitude": {
                                "type": [
                                    "number",
                                    "null"
                                ]
                            },
                            "longitude": {
                                "type": [
                                    "number",
                                    "null"
                                ]
                            }
                        },
                        "required": [
                            "slug",
                            "name",
                            "locality",
                            "latitude",
                            "longitude"
                        ]
                    },
                    "feed_captured_at": {
                        "type": "string",
                        "description": "API: When the prices behind this provider were captured. Null before its first sync."
                    }
                },
                "required": [
                    "id",
                    "identifier",
                    "name",
                    "display_name",
                    "region",
                    "retailer",
                    "store",
                    "feed_captured_at"
                ],
                "title": "PriceProviderResource"
            },
            "RecipeCommentResource": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "string"
                    },
                    "body": {
                        "type": "string"
                    },
                    "user": {
                        "$ref": "#\/components\/schemas\/UserResource"
                    },
                    "created_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    }
                },
                "required": [
                    "id",
                    "body",
                    "created_at"
                ],
                "title": "RecipeCommentResource"
            },
            "RecipeResource": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "string"
                    },
                    "family_id": {
                        "anyOf": [
                            {
                                "type": "string"
                            },
                            {
                                "type": "integer"
                            }
                        ]
                    },
                    "web_url": {
                        "type": "string"
                    },
                    "title": {
                        "type": "string"
                    },
                    "description": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "cuisine": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "tags": {
                        "anyOf": [
                            {
                                "type": "string"
                            },
                            {
                                "type": "array",
                                "items": {
                                    "type": "string"
                                },
                                "minItems": 0,
                                "maxItems": 0,
                                "additionalItems": false
                            }
                        ]
                    },
                    "source_type": {
                        "type": "string"
                    },
                    "source_url": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "source_name": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "prep_time_minutes": {
                        "type": [
                            "integer",
                            "null"
                        ]
                    },
                    "cook_time_minutes": {
                        "type": [
                            "integer",
                            "null"
                        ]
                    },
                    "total_time_minutes": {
                        "type": [
                            "integer",
                            "null"
                        ]
                    },
                    "servings": {
                        "type": "integer"
                    },
                    "photos": {
                        "type": "array",
                        "items": {
                            "type": "string"
                        }
                    },
                    "is_stylizing_hero": {
                        "type": "boolean"
                    },
                    "sketch_url": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Additive, dig #1623. The single pencil-line sketch: the rest\nstate of a recipe under The Saucery's art direction, which\nblooms into the watercolour on press. Null until one has been\nrendered \u2014 a recipe with no sketch stays a ruled empty card,\nnever a grey placeholder. Deliberately NOT in photos[], which\nis the user-facing gallery."
                    },
                    "is_sketching": {
                        "type": "boolean"
                    },
                    "notes": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "image_prompt": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "visibility": {
                        "type": "string"
                    },
                    "is_favourite": {
                        "type": "boolean",
                        "description": "Coerce to a real bool: a freshly-created model (e.g. the import\nconfirm response) hasn't hydrated these DB-defaulted columns, so\nthe raw attribute is null. Clients decode these as non-optional\nbooleans, so a null breaks the decode. (#101)"
                    },
                    "needs_review": {
                        "type": "boolean"
                    },
                    "ingredients": {
                        "type": "array",
                        "items": {
                            "$ref": "#\/components\/schemas\/IngredientResource"
                        }
                    },
                    "steps": {
                        "type": "array",
                        "items": {
                            "$ref": "#\/components\/schemas\/RecipeStepResource"
                        }
                    },
                    "last_cooked_at": {
                        "type": "string"
                    },
                    "times_cooked": {
                        "type": "integer",
                        "minimum": 0
                    },
                    "comments": {
                        "type": "array",
                        "items": {
                            "$ref": "#\/components\/schemas\/RecipeCommentResource"
                        }
                    },
                    "created_by": {
                        "$ref": "#\/components\/schemas\/UserResource"
                    },
                    "lab_items": {
                        "type": "array",
                        "items": {
                            "$ref": "#\/components\/schemas\/LabItemResource"
                        }
                    },
                    "created_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    },
                    "updated_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    },
                    "is_starter": {
                        "type": "boolean",
                        "description": "A new family's book opens with two sample recipes (dig #3500).\nAPI: True for one of the sample recipes every new family's book starts with, which the family did not add. Always present."
                    },
                    "byline": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "API: Where the recipe came from when it is not the family's own, ready to show under the title, e.g. \"From The Saucery\". Null for the family's own recipes."
                    },
                    "scales": {
                        "type": "boolean",
                        "description": "Coerced: a recipe built but never saved has no value yet.\nAPI: True when a planned meal of this recipe is cooked for the household and starts at the family's default servings. False for a batch, such as a cake or a loaf, which is planned at the recipe's own servings. Always present."
                    },
                    "shared_by_name": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "API: The name of the person who sent the link this recipe was saved from, e.g. \"Grace\". Null for a recipe that was not saved from a share link. Separate from source_name, which says where the recipe originally came from."
                    },
                    "grocery_specials": {
                        "type": "array",
                        "items": {
                            "type": "string"
                        },
                        "minItems": 0,
                        "maxItems": 0,
                        "additionalItems": false
                    }
                },
                "required": [
                    "id",
                    "family_id",
                    "web_url",
                    "title",
                    "description",
                    "cuisine",
                    "tags",
                    "source_type",
                    "source_url",
                    "source_name",
                    "prep_time_minutes",
                    "cook_time_minutes",
                    "total_time_minutes",
                    "servings",
                    "photos",
                    "is_stylizing_hero",
                    "sketch_url",
                    "is_sketching",
                    "notes",
                    "image_prompt",
                    "visibility",
                    "is_favourite",
                    "needs_review",
                    "created_at",
                    "updated_at",
                    "is_starter",
                    "byline",
                    "scales",
                    "shared_by_name",
                    "grocery_specials"
                ],
                "title": "RecipeResource"
            },
            "RecipeStepResource": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "string"
                    },
                    "order": {
                        "type": "integer"
                    },
                    "instruction": {
                        "type": "string"
                    },
                    "duration_seconds": {
                        "type": [
                            "integer",
                            "null"
                        ]
                    },
                    "ingredient_ids": {
                        "anyOf": [
                            {
                                "type": "array",
                                "items": {
                                    "type": "string"
                                }
                            },
                            {
                                "type": "null"
                            },
                            {
                                "type": "array",
                                "items": {}
                            }
                        ]
                    },
                    "timers": {
                        "type": "array",
                        "items": {}
                    }
                },
                "required": [
                    "id",
                    "order",
                    "instruction",
                    "duration_seconds",
                    "ingredient_ids",
                    "timers"
                ],
                "title": "RecipeStepResource"
            },
            "ShoppingListItemResource": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "string"
                    },
                    "name": {
                        "type": "string"
                    },
                    "quantity": {
                        "type": "string",
                        "description": "The row stores the exact sum of its recipes (\u2154 of a lemon); this is\nwhat to buy, rounded up to something a shelf sells. Shipped clients\nprint it as they are given it.\nAPI: What to buy: a count is whole and a weight or volume is rounded up to what a shelf sells.\nA line the family typed in is returned as typed."
                    },
                    "unit": {
                        "type": "string"
                    },
                    "display_quantity": {
                        "type": "string",
                        "description": "Shipped clients format quantity and unit themselves; newer ones print\nthese instead, so the buy-unit wording is decided once, here (dig #3585).\nAPI: The amount as it reads before the name, in the unit it is bought in: \"2.1 l\", \"5 tbsp\", or\n\"about 5\" for a weight of something bought by the piece. Null when the line has no amount."
                    },
                    "quantity_note": {
                        "type": "string",
                        "description": "API: A second line about the amount, or null: the weight behind an \"about\" count (\"540 g\n altogether\"), or \"You probably have this in the pantry\" when probably_at_home is true."
                    },
                    "probably_at_home": {
                        "type": "string",
                        "description": "API: True for a spoonful of something from the pantry aisles, or a splash of oil or sauce, that most\nkitchens already have. The line stays on the list."
                    },
                    "group": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "is_checked": {
                        "type": "boolean"
                    },
                    "checked_by": {
                        "type": "string"
                    },
                    "checked_by_name": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "API: The name of the family member who ticked the line, or null when it is not ticked or the tick\npredates the record."
                    },
                    "checked_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "API: When the line was ticked, ISO 8601, or null."
                    },
                    "added_by": {
                        "type": [
                            "object",
                            "null"
                        ],
                        "description": "API: Who put the line on the list, {id, name}: the person who typed it, asked the assistant for it, or\nadded the plan, recipe or staples it came from. Null for lines added before this was recorded and for\nlines added through an integration key.",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "name": {
                                "type": "string"
                            }
                        },
                        "required": [
                            "id",
                            "name"
                        ]
                    },
                    "recipe_id": {
                        "type": "string"
                    },
                    "staple_id": {
                        "type": "string"
                    },
                    "is_manual": {
                        "type": "boolean"
                    },
                    "is_skipped": {
                        "type": "boolean"
                    },
                    "source_type": {
                        "type": "string",
                        "enum": [
                            "manual",
                            "recipe",
                            "staple"
                        ]
                    },
                    "recipe_names": {
                        "type": "object",
                        "additionalProperties": {}
                    },
                    "contributions": {
                        "type": "array",
                        "description": "API: Per-recipe prep sub-lines, e.g. {recipe_name: \"Roast Chicken Dinner\",\ndetail: \"sliced, raw\", date: \"2026-10-12\"}. Lets clients render the base name as the row with\neach recipe's specifics underneath. date is the night the recipe is planned for (Y-m-d), the\nearliest when the week has it twice, and null for a recipe added from its own page.",
                        "items": {
                            "type": "object",
                            "properties": {
                                "recipe_id": {
                                    "type": "string"
                                },
                                "recipe_name": {
                                    "type": "string"
                                },
                                "detail": {
                                    "type": "string"
                                },
                                "date": {
                                    "type": [
                                        "string",
                                        "null"
                                    ]
                                }
                            },
                            "required": [
                                "recipe_id",
                                "recipe_name",
                                "detail",
                                "date"
                            ]
                        }
                    },
                    "is_on_special": {
                        "type": "boolean"
                    },
                    "price": {
                        "type": [
                            "string",
                            "null"
                        ]
                    }
                },
                "required": [
                    "id",
                    "name",
                    "quantity",
                    "unit",
                    "display_quantity",
                    "quantity_note",
                    "probably_at_home",
                    "group",
                    "is_checked",
                    "checked_by",
                    "checked_by_name",
                    "checked_at",
                    "added_by",
                    "recipe_id",
                    "staple_id",
                    "is_manual",
                    "is_skipped",
                    "source_type",
                    "is_on_special",
                    "price"
                ],
                "title": "ShoppingListItemResource"
            },
            "ShoppingListResource": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "string"
                    },
                    "family_id": {
                        "anyOf": [
                            {
                                "type": "string"
                            },
                            {
                                "type": "integer"
                            }
                        ]
                    },
                    "week_start_date": {
                        "type": "string"
                    },
                    "items": {
                        "type": "array",
                        "items": {
                            "$ref": "#\/components\/schemas\/ShoppingListItemResource"
                        }
                    },
                    "checked_count": {
                        "type": "integer",
                        "minimum": 0
                    },
                    "total_count": {
                        "type": "integer",
                        "minimum": 0
                    },
                    "meals": {
                        "type": "object",
                        "description": "Built from the item-to-recipe pivot and the plan's cooks: see ShoppingListMeals.\nAPI: The meals the list is buying for, one per recipe and the night it is planned for, ordered by\nnight then recipe name; a recipe added from its own page comes last with date null. Each is\n{key, date, recipe_id, recipe_name, cook, item_ids, checked_count, item_count}. key is stable for\nthe (recipe, night) pair, cook is the planned meal's cook ({id, name, user_id, pending}, as on a\nmeal plan entry) or null, and item_ids are the ids of its lines in items. A line two meals share\nis in both. Present when items are.",
                        "additionalProperties": {
                            "type": "string"
                        }
                    },
                    "created_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    },
                    "updated_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    },
                    "price_summary": {
                        "type": [
                            "object",
                            "null"
                        ],
                        "properties": {
                            "estimated_total": {
                                "type": "number"
                            },
                            "currency": {
                                "type": "string",
                                "const": "NZD"
                            },
                            "priced_count": {
                                "type": "integer"
                            },
                            "item_count": {
                                "type": "integer"
                            },
                            "remaining_total": {
                                "type": "number"
                            },
                            "remaining_count": {
                                "type": "integer"
                            },
                            "remaining_item_count": {
                                "type": "string"
                            },
                            "checked_total": {
                                "type": "number"
                            },
                            "checked_count": {
                                "type": "integer"
                            }
                        },
                        "required": [
                            "estimated_total",
                            "currency",
                            "priced_count",
                            "item_count",
                            "remaining_total",
                            "remaining_count",
                            "remaining_item_count",
                            "checked_total",
                            "checked_count"
                        ]
                    }
                },
                "required": [
                    "id",
                    "family_id",
                    "week_start_date",
                    "created_at",
                    "updated_at",
                    "price_summary"
                ],
                "title": "ShoppingListResource"
            },
            "UserResource": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "string"
                    },
                    "name": {
                        "type": "string"
                    },
                    "email": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "avatar": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "dietary_preferences": {
                        "type": [
                            "array",
                            "null"
                        ],
                        "items": {}
                    },
                    "locale": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "region": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "region_source": {
                        "type": "string",
                        "description": "API: 'user' once someone has set the region themselves, 'device' while it follows the phone."
                    },
                    "timezone": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "measurement_system": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "email_weekly_specials": {
                        "type": "boolean"
                    },
                    "email_product_tips": {
                        "type": "boolean"
                    },
                    "push_meal_reminders": {
                        "type": "boolean",
                        "description": "API: The morning push naming tonight's dinner (and tomorrow's). On unless turned off."
                    },
                    "family": {
                        "$ref": "#\/components\/schemas\/FamilyResource"
                    },
                    "created_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    }
                },
                "required": [
                    "id",
                    "name",
                    "email",
                    "avatar",
                    "dietary_preferences",
                    "locale",
                    "region",
                    "region_source",
                    "timezone",
                    "measurement_system",
                    "email_weekly_specials",
                    "email_product_tips",
                    "push_meal_reminders",
                    "created_at"
                ],
                "title": "UserResource"
            }
        },
        "responses": {
            "ModelNotFoundException": {
                "description": "Not found",
                "content": {
                    "application\/json": {
                        "schema": {
                            "type": "object",
                            "properties": {
                                "message": {
                                    "type": "string",
                                    "description": "Error overview."
                                }
                            },
                            "required": [
                                "message"
                            ]
                        }
                    }
                }
            },
            "ValidationException": {
                "description": "Validation error",
                "content": {
                    "application\/json": {
                        "schema": {
                            "type": "object",
                            "properties": {
                                "message": {
                                    "type": "string",
                                    "description": "Errors overview."
                                },
                                "errors": {
                                    "type": "object",
                                    "description": "A detailed description of each field that failed validation.",
                                    "additionalProperties": {
                                        "type": "array",
                                        "items": {
                                            "type": "string"
                                        }
                                    }
                                }
                            },
                            "required": [
                                "message",
                                "errors"
                            ]
                        }
                    }
                }
            }
        }
    }
}