DEV Community

Cover image for FoodFacts API - AI-Powered Nutrition & Recipe REST API
Mohit Bisht
Mohit Bisht

Posted on

FoodFacts API - AI-Powered Nutrition & Recipe REST API

Xano AI-Powered Backend Challenge: Public API Submission

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
Enter fullscreen mode Exit fullscreen mode

FoodFacts API Group URL:

https://x95f-42hw-risi.m2.xano.io/api:GZXOANAS
Enter fullscreen mode Exit fullscreen mode

Base URL:

https://x95f-42hw-risi.m2.xano.io/api
Enter fullscreen mode Exit fullscreen mode

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."
}
Enter fullscreen mode Exit fullscreen mode

API Endpoints

/nutrition/ingredients

Purpose: Analyze ingredients and return total calories & macros

Method: POST

Example Input:

{
  "ingredients": [
    "1 medium banana",
    "2 tbsp peanut butter"
  ]
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

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"]
}
Enter fullscreen mode Exit fullscreen mode

Developers can send a simple list of ingredients, and the API returns complete recipes with steps and nutrition.

Demo

Video Link

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"
Enter fullscreen mode Exit fullscreen mode

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"}'
Enter fullscreen mode Exit fullscreen mode

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"
    ]
  }'
Enter fullscreen mode Exit fullscreen mode

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"
  }'
Enter fullscreen mode Exit fullscreen mode

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"
  }'
Enter fullscreen mode Exit fullscreen mode

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
  }'
Enter fullscreen mode Exit fullscreen mode

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"
    ]
  }'
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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:

  1. One or two function blocks per endpoint
  2. Basic create/read/update/delete logic
  3. 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
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

⚙️ Challenge 2: Schema Inconsistencies

The above XanoScript had some issues:

  1. The prompt explicitly referenced the endpoint name nutrition/ingredients, but the logic assistant named it estimate_nutrition

  2. The 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 }
]
Enter fullscreen mode Exit fullscreen mode

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 }
]
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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:

  1. dev.to/mohitb_twt - mail.mohitbisht@gmail.com
  2. dev.to/deebi9 - deepanshudb1@gmail.com

Top comments (0)