This is a submission for the Xano AI-Powered Backend Challenge: Production-Ready Public API
What I Built
FoodFacts API is a production-ready public REST API that provides nutrition, meal, and food data powered by AI. It returns structured results that apps can rely on.
Developers can use the API to analyze food, estimate calories and macronutrients, generate diet plans, and create recipes from natural language input. The API is stateless and built for real-world use in fitness apps, diet planners, recipe platforms, and health dashboards.
FoodFacts API combines AI reasoning with strict JSON schemas to ensure responses stay predictable and easy to parse.
Architecture Diagram
What Data Services Does FoodFacts API Provide?
FoodFacts API supports these services:
- Food nutrition data such as calories, protein, carbs, and fats
- Meal-level macro estimates from free-text descriptions
- Ingredient-based nutrition analysis
- Goal-based diet plan generation
- Recipe generation from available ingredients
All responses return clean, structured JSON designed for direct use in web and mobile apps.
Key Features
Food Nutrition Search
Developers can search nutrition data for common foods. This supports calorie tracking and food databases without extra setup.
Meal and Ingredient Analysis
The API estimates nutrition from:
- A list of ingredients
- A natural language meal description
This removes the need to maintain large food databases.
AI-Generated Diet Plans
Based on user goals such as weight loss or muscle gain, the API generates multi-day plans that include:
- Daily meals
- Estimated calories
- Grocery lists
- Health tips
Recipe Generation
Given a list of ingredients, the API returns:
- Recipe ideas
- Preparation steps
- Estimated nutrition
Consistent JSON Responses
All endpoints enforce strict JSON schemas. The API avoids unstructured AI output so responses stay safe and predictable.
Who Is This API For?
FoodFacts API fits teams building:
- Fitness and calorie-tracking apps
- Diet and wellness platforms
- Recipe and meal-planning tools
- Health-focused SaaS products
- AI-powered food features
API Documentation
Base URLs
Authentication Group URL:
https://x95f-42hw-risi.m2.xano.io/api:QC35j52Y
FoodFacts API Group URL:
https://x95f-42hw-risi.m2.xano.io/api:GZXOANAS
Base URL:
https://x95f-42hw-risi.m2.xano.io/api
Authentication Strategy
FoodFacts API uses a hybrid authentication model:
-
Public endpoints allow anonymous access for testing and basic use. These include:
foods/search,nutrition/estimate&nutrition/ingredients -
Protected endpoints require Bearer tokens. These include:
diet/plan,recipes/from-ingredients
This approach keeps onboarding simple while protecting AI-heavy operations.
Rate Limiting
To protect reliability and cost, we apply rate limiting to prevent abuse and stay within Gemini API limits (15 RPM for Flash).
redis.ratelimit {
key = "recipes_fromingr_limit:" ~ $auth.id
max = 15
ttl = 60
error = "Too many requests. Please try again in a minute."
}
API Endpoints
/nutrition/ingredients
Purpose: Analyze ingredients and return total calories & macros
Method: POST
Example Input:
{
"ingredients": [
"1 medium banana",
"2 tbsp peanut butter"
]
}
Here we send a simple list of ingredients with quantities, and the API returns total calories, protein, carbs, and fats.
/foods/search
Purpose: Search nutrition data for a food item
Method: GET
Example Input:
/foods/search?query=apple
This endpoint lets developers search for standardized nutrition data for a food item, normalized.
/nutrition/estimate
Purpose: Estimate calories & macros from free-text
Method: POST
Example Input:
{
"meal_description": "2 slices of pizza and a glass of coke"
}
Even when exact ingredients aren't known, developers can send a natural language description and get an estimated nutritional breakdown.
/diet/plan
Purpose: Generate a multi-day diet plan
Method: POST
Authentication: Required
Example Input:
{
"goal": "weight loss",
"days": 3,
"diet_type": "vegetarian"
}
Based on the user's goal and preferences, the API generates a complete multi-day diet plan.
/recipes/from-ingredients
Purpose: Generate recipes from available ingredients
Method: POST
Authentication: Required
Example Input:
{
"ingredients": ["chicken", "rice", "onion"]
}
Developers can send a simple list of ingredients, and the API returns complete recipes with steps and nutrition.
Demo
Using Public Endpoints
1. Food Search
curl -X GET \
"https://x95f-42hw-risi.m2.xano.io/api:GZXOANAS/foods/search?query=banana" \
-H "Content-Type: application/json"
2. Nutrition Estimate
curl -X POST \
'https://x95f-42hw-risi.m2.xano.io/api:GZXOANAS/nutrition/estimate' \
-H 'Content-Type: application/json' \
--data '{"meal_description":"Grilled chicken breast with brown rice and steamed broccoli"}'
3. Ingredient Analysis
curl -X POST \
https://x95f-42hw-risi.m2.xano.io/api:GZXOANAS/nutrition/ingredients \
-H "Content-Type: application/json" \
-d '{
"ingredients": [
"1 medium banana",
"2 tbsp peanut butter"
]
}'
Using Protected Endpoints
To use protected endpoints, you first need to create an AUTH Token using auth/signup & auth/login endpoints.
Step 1: Sign Up
curl -X POST https://x95f-42hw-risi.m2.xano.io/api:QC35j52Y/auth/signup \
-H "Content-Type: application/json" \
-d '{
"email": "test1@gg.com",
"name": "Test",
"password": "testpassword"
}'
Step 2: Login
curl -X POST https://x95f-42hw-risi.m2.xano.io/api:QC35j52Y/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "test1@gg.com",
"password": "testpassword"
}'
Copy the "authToken" from the login response and use it in the following requests.
3. Diet Plan Generation
curl -X POST \
https://x95f-42hw-risi.m2.xano.io/api:GZXOANAS/diet/plan \
-H "Authorization: Bearer YOUR_AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"goal": "weight_loss",
"days": 30
}'
4. Recipe Generation
curl -X POST \
https://x95f-42hw-risi.m2.xano.io/api:GZXOANAS/recipes/from-ingredients \
-H "Authorization: Bearer YOUR_AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"ingredients": [
"tomato",
"cheese",
"bread"
]
}'
The AI Prompt I Used
Build a production-ready public REST API called "FoodFacts API".
This is a backend-only service (no UI) designed for third-party developers
building fitness, health, diet, and food applications.
The API must return structured JSON only and be ready for real-world usage.
Core features and endpoints:
1. POST /nutrition/ingredients
Input: { ingredients: [string] }
Behavior:
- Accept a list of ingredients such as "2 eggs", "1 cup milk"
- Use AI to estimate nutrition
- Return total calories, protein, carbs, fats
- Include a per-ingredient breakdown
2. GET /foods/search?query=
Behavior:
- Search a food item by name
- Return nutrition facts per serving and per 100g
- Include category and portion information
3. POST /nutrition/estimate
Input: { meal_description: string }
Behavior:
- Accept a natural language meal description
- Estimate total calories, protein, carbs, and fats
4. POST /diet/plan
Input: { goal: string, days: number }
Behavior:
- Generate a day-wise AI-powered diet plan
- Include meals per day
- Include daily calorie targets
- Provide a grocery list and health tips
5. POST /recipes/from-ingredients
Input: { ingredients: [string] }
Behavior:
- Generate 3–5 recipes using the provided ingredients
- Include steps and estimated nutrition for each recipe
Technical requirements:
- Use API-first design
- Structured JSON responses only
- Clear request and response schemas
- Error handling for invalid input
- Ready for API key authentication and rate limiting
- No frontend or UI logic
This API should demonstrate best practices for a consumable public API
built with Xano and AI-powered external requests.
AI-Generated Database Schema
How I Refined the AI-Generated Code
⚙️ Challenge 1: Auto-Generated CRUD Endpoints
The platform automatically created ~20 CRUD-style API endpoints based on the prompt. These endpoints followed a standard pattern:
- One or two function blocks per endpoint
- Basic create/read/update/delete logic
- No business rules, validations, or AI orchestration
Instead of that, we manually designed and implemented the core APIs that actually power the product.
For example, it created an endpoint called diet_plan, which looked like this:
// Query all diet_plan records
query diet_plan verb=GET {
api_group = "FoodFacts API Group"
input {
}
stack {
db.query diet_plan {
return = {type: "list"}
} as $diet_plan
}
response = $diet_plan
}
Good part 😄: Later we used the Logic Assistant to create multiple endpoints by describing the intent of each API in natural language. The assistant generated accurate function blocks such as input validation, conditional logic, external API requests, and response handling.
The PROMPT we used with Logic Assistant looked like this:
You are building logic for a public API endpoint: POST /nutrition/ingredients.
Goal:
Analyze a list of food ingredients and return estimated nutrition using AI.
Input:
- ingredients: array of strings (required)
Example: ["2 eggs", "1 cup milk", "1 banana"]
Logic requirements:
1. Validate input:
- ingredients must be an array
- array must not be empty
- return a clear JSON error if invalid
2. Build an AI prompt that asks the model to:
- Estimate calories, protein, carbs, and fats for each ingredient
- Calculate total calories, protein, carbs, and fats
- Return JSON only (no explanations, no markdown)
Expected AI response format:
{
"total_calories": number,
"total_protein": number,
"total_carbs": number,
"total_fats": number,
"breakdown": [
{
"item": string,
"calories": number,
"protein": number,
"carbs": number,
"fats": number
}
]
}
3. Use an external API request to call an AI model
(Google Gemini 1.5 Flash or equivalent) to generate nutrition estimates.
4. Parse and validate the AI JSON response.
- If parsing fails, return a graceful error message.
5. Return a clean JSON response with:
- total calories
- total protein
- total carbs
- total fats
- per-ingredient breakdown
Additional constraints:
- No database writes required
- Stateless request
- JSON responses only
- Production-ready error handling
- Optimized for a public, API-key protected endpoint
The output Logic Assistant generated was this:
// Estimates nutrition facts for a list of ingredients using AI.
query estimate_nutrition verb=POST {
api_group = "FoodFacts API Group"
input {
// List of ingredients to analyze (e.g., "1 cup rice", "100g chicken")
text[] ingredients
}
stack {
// Validate that the ingredients array is not empty
precondition (($input.ingredients | count) > 0) {
error_type = "inputerror"
error = "Ingredients list cannot be empty."
}
// Construct the AI prompt
var $prompt {
value =
"Estimate the nutrition facts for these ingredients: "
~ ($input.ingredients | join: ", ")
~ ". Return strictly a JSON object with keys: "
~ "total_calories (int), total_protein_g (decimal), "
~ "total_carbs_g (decimal), total_fats_g (decimal), "
~ "and ingredients (array of objects with name, calories, "
~ "protein_g, carbs_g, fats_g). "
~ "Do not use markdown formatting."
}
// Call Google Gemini API
api.request {
url =
"https://generativelanguage.googleapis.com/v1beta/models/"
~ "gemini-2.5-flash:generateContent?key="
~ $env.GEMINI_API_KEY
method = "POST"
params = {
contents: [
{
parts: [
{ text: $prompt }
]
}
]
}
headers = [] | push: "Content-Type: application/json"
} as $ai_response
// Parse the response
try_catch {
try {
var $raw_content {
value =
$ai_response.response.result.candidates[0]
.content.parts[0].text
}
// Clean potential markdown formatting just in case
var $clean_content {
value =
$raw_content
| replace: "```
json", ""
| replace: "
```", ""
| trim
}
var $nutrition_data {
value = $clean_content | json_decode
}
}
catch {
throw {
name: "inputerror"
value = "Unable to process nutrition data from AI response."
}
}
}
}
response = $nutrition_data
}
⚙️ Challenge 2: Schema Inconsistencies
The above XanoScript had some issues:
The prompt explicitly referenced the endpoint name
nutrition/ingredients, but the logic assistant named itestimate_nutritionThe output schema AI generated was (this was the biggest difference):
total_calories (int)
total_protein_g (decimal)
total_carbs_g (decimal)
total_fats_g (decimal)
ingredients: [
{ name, calories, protein_g, carbs_g, fats_g }
]
However, it should have been the following, as stated in the prompt schema:
total_calories (number)
total_protein (number)
total_carbs (number)
total_fats (number)
breakdown: [
{ item, calories, protein, carbs, fats }
]
The refined XanoScript we used:
// Estimates nutritional information for a list of ingredients using AI.
query "nutrition/ingredients" verb=POST {
api_group = "FoodFacts API Group"
input {
// List of ingredients to analyze (e.g., ["1 cup rice", "100g chicken"])
text[] ingredients
}
stack {
// Validate that the input array is not empty
precondition (($input.ingredients | count) > 0) {
error_type = "inputerror"
error = "The ingredients list cannot be empty."
}
// Construct the prompt for the AI model
var $prompt {
value =
"Analyze the following ingredients: "
~ ($input.ingredients | join: ", ")
~ ". Provide a nutritional estimation. "
~ "Return ONLY a raw JSON object (no markdown formatting, no code blocks) "
~ "adhering strictly to this schema: "
~ "{ \"total_calories\": number, "
~ "\"total_protein\": number, "
~ "\"total_carbs\": number, "
~ "\"total_fats\": number, "
~ "\"breakdown\": [ "
~ "{ \"item\": string, \"calories\": number, "
~ "\"protein\": number, \"carbs\": number, \"fats\": number } "
~ "] }. "
~ "Ensure all numeric values are returned as numbers, not strings."
}
// Call Google Gemini 2.5 Flash API
api.request {
url =
"https://generativelanguage.googleapis.com/v1beta/models/"
~ "gemini-2.5-flash:generateContent?key="
~ $env.GEMINI_API_KEY
method = "POST"
params = {
contents: [
{
parts: [
{ text: $prompt }
]
}
]
}
headers = [] | push: "Content-Type: application/json"
} as $gemini_response
// Extract the raw text from the response
var $raw_content {
value =
$gemini_response.response.result
.candidates[0].content.parts[0].text
}
// Clean the response text by removing markdown artifacts if present
var $cleaned_content {
value =
$raw_content
| replace: "```
json", ""
| replace: "
```", ""
| trim
}
// Attempt to parse the cleaned text into a JSON object
try_catch {
try {
var $nutrition_data {
value = $cleaned_content | json_decode
}
}
catch {
throw {
name = "ParsingError"
value =
"Failed to parse AI response into valid JSON. Raw response: "
~ $cleaned_content
}
}
}
}
response = $nutrition_data
}
Similarly, we made improvements to other endpoints as well.
⚙️ Challenge 3: API Rate Limiting
One of the main challenges we faced was working within the API limits of Gemini 2.5 Flash. Since multiple endpoints depend on AI-generated responses, we had to be careful about how often requests were made and ensure that rate limiting was applied consistently. This required us to design our endpoints defensively, validate inputs early, and avoid unnecessary AI calls. Handling structured JSON responses reliably from the AI model also required iterative prompt refinement to keep outputs predictable for a production API.
⚙️ Challenge 4: Exploring Xano Actions
We also explored using Xano Actions to abstract shared logic such as validation and response normalization. While the concept is powerful, we found that integrating Actions into existing endpoints required careful understanding of how data flows between Actions and API stacks. Although Xano provides resources and videos around Actions, we realized that for this project, keeping the logic directly inside endpoints allowed us to move faster and maintain clarity. This exploration was still valuable, as it helped us better understand when Actions are most appropriate in real-world backends.
My Experience with Xano
What Helped Most When Using Xano
- Visual logic builder for clear and debuggable flows
- Built-in debugger for inspecting live requests and responses
- Simple external API integration with secure key storage
- Built-in authentication and rate limiting
- Structured error handling
- Fast iteration from idea to stable API
The Logic Assistant was particularly valuable for rapidly prototyping endpoint logic. While it didn't always produce perfect code on the first try, it significantly reduced development time by generating a solid foundation that we could refine.
The visual nature of Xano's function stack builder made it easy to understand data flow and debug issues. Combined with the built-in request debugger, we could quickly identify and fix problems in our API logic.
Team Submission:
- dev.to/mohitb_twt - mail.mohitbisht@gmail.com
- dev.to/deebi9 - deepanshudb1@gmail.com



Top comments (0)