Find recipes by the ingredients you already have
Send what's in the kitchen, get recipes ranked by what you already have.
"What can I cook with what I've got?" is one of the most useful things a food app can answer. It's also one of the fiddliest to build yourself.
It sounds simple. Take the user's ingredients, find recipes that use them. But real kitchens are messy. Almost every recipe wants salt. Does that mean everyone is "missing" salt? Someone types "chicken", but the recipe says "chicken thighs". Is that a match? And a recipe that uses one of your five ingredients isn't really a dinner idea, even if it technically "uses what you have".
So in Tiny Plates we put all of that in one endpoint: /pantry. You send what's in the kitchen. You get recipes back, ranked by how much of each one you already have.
curl 'https://api.tinyplates.dev/pantry?ingredients=chicken,rice,onion&maxMissing=2&language=en' \
-H 'Authorization: Bearer rd_your_api_key'
Every result is a normal recipe object with four extra fields. Here's a real one from that call, trimmed:
{
"title": "Buttermilk-Marinated Air Fryer Whole Roasted Chicken",
"coverage": 0.5,
"matchedIngredients": ["chicken"],
"missingIngredients": ["buttermilk"],
"assumedIngredients": ["kosher salt"]
}
matchedIngredients is what you have. missingIngredients is your shopping list. coverage is the share of the recipe your pantry covers, from 0 to 1. And assumedIngredients is the stuff we figured you already have.
How the matching works
Let's go through the messy bits one at a time.
Staples. Salt, pepper, water, oil, sugar and flour count as already in your kitchen. A few common versions too, like olive oil, kosher salt and black pepper. They go in assumedIngredients, and they don't count towards coverage or the missing list. So a recipe isn't "two things short" just because it wants salt and pepper. If your app keeps a real inventory and you want every ingredient counted, send staples=false.
Names. You can send plain names or canonical ingredient ids. We lowercase them, turn spaces into hyphens and handle simple plurals, so "Eggs" finds recipes with "egg". Some common synonyms are folded together as well, like scallion and spring onion, or feta cheese and feta.
"Chicken" vs "chicken thighs". This one is on purpose, and worth knowing. A match is exact on the ingredient, not a fuzzy text search. So "chicken" matches recipes that use chicken, like a whole roast chicken. It does not match a recipe that calls for chicken breast. We think that's the honest answer, since a whole bird and a pack of breasts aren't the same shopping trip. But it means your app should send what people actually have. If someone has "chicken", it's fine to send chicken,chicken-breast,chicken-thigh. The /ingredients endpoint lists the ingredient ids, most used first, which is handy for an autocomplete. If you're curious how we get from "2 large Vidalia onions, sliced" to plain onion, we wrote about parsing ingredient lines.
Ranking. Only recipes that use at least one of your ingredients come back. The best covered come first, and on a tie, the one with fewer missing things wins. maxMissing cuts the list to recipes you're short at most that many ingredients for. maxMissing=0 means "what can I cook right now".
The other parameters are the usual ones. limit (20 by default, up to 100) and offset for paging. language for English, Spanish or Swedish recipes. And without to leave out recipes with allergens, like without=milk,peanuts. You can send up to 20 ingredients per call. The pantry reference has every parameter and the full response.
A small "what can I cook?" function
Here's a function you could drop into a server route. It keeps the key on the server, asks for up to two missing ingredients, and splits the answer into "ready now" and "almost there":
interface PantryRecipe {
coverage: number
id: string
matchedIngredients: string[]
missingIngredients: string[]
title: string
url: string
}
export async function whatCanICook(pantry: string[]) {
const params = new URLSearchParams({
ingredients: pantry.join(','),
language: 'en',
limit: '50',
maxMissing: '2'
})
const response = await fetch(`https://api.tinyplates.dev/pantry?${params}`, {
headers: { Authorization: `Bearer ${process.env.TINYPLATES_API_KEY}` }
})
if (!response.ok) {
const body = await response.json().catch(() => null)
throw new Error(body?.error?.message ?? `Tiny Plates answered ${response.status}.`)
}
const { recipes } = (await response.json()) as { recipes: PantryRecipe[] }
// A recipe that only uses one thing you have isn't much of a dinner idea.
const meals = recipes.filter((recipe) => recipe.matchedIngredients.length >= 2)
return {
almost: meals.filter((recipe) => recipe.missingIngredients.length > 0),
readyNow: meals.filter((recipe) => recipe.missingIngredients.length === 0)
}
}
That filter line is there for a reason. Coverage is a share, so small recipes score really well. Send eggs, garlic and a few other things, and hard-boiled eggs can come out on top with a coverage of 1. Technically correct! Not very helpful at 7pm, though. Asking for a bigger page and keeping recipes that use at least two of your ingredients fixes most of it. You could also look at each recipe's courses and drop side dishes if you only want mains.
The API's error messages are written for people, so passing error.message straight through is fine. Send 21 ingredients and the user is told the limit, not "400".
Want to see it working? This is Fridge Roulette, a little demo built on /pantry. Type in what's in your fridge and spin:
Fridge Roulette
Live, on the real API. Go on, give it a spin.
It asks for 12 recipes with maxMissing set to 0 or 1, spins through them, and then calls /substitutions for whatever the winner is missing. The Fridge Roulette guide has every line of the React code, so we won't repeat it here.
Tips for a good pantry screen
A few things we learned while building it:
- Lead with what's missing. "You're missing 2 things: buttermilk, lemon" is the most useful line on the card. People decide based on that, not on a percentage.
- Default to staples on. Nobody wants a list of recipes that are "missing salt". Only turn staples off if your app really knows what's in the cupboard.
- Don't show a blank screen. With
maxMissing=0and a strange fridge, you can get nothing back. Say so, and offer to allow one missing ingredient. - Suggest a swap. If the missing thing is butter,
/substitutionscan tell you to use olive oil instead, and when that's a bad idea. That turns "almost" into "ready now". - Turn "almost" into a list. Send the recipe to
/shopping-listand the missing bits come back sorted by aisle.
Each pantry search is one request. The free plan gives you 50 a day and 300 a week, which is plenty to build and test with, and we raise it on request. If you're coming from Yummly's recipes-by-ingredients search, we also wrote up what to use now that Yummly is gone.
Grab a free key and see what your fridge can do.