Skip to main content

About

This guide provides complete implementation details and best practices for integrating the Nutrition API into your applications. For detailed API specifications and data types, refer to the API Reference.
Authentication: The code examples in this guide focus on the nutrition analysis functionality. For authentication implementation details, see the Authentication documentation. You’ll need to add Bearer token into the Authorization header to all API requests.

API Endpoints

The Nutrition API provides these endpoints for managing nutrition records:

Image Preparation

For optimal image capture guidelines, see the Image Guidelines in the API overview.

POST Request Body

Processing Modes

The processing mode is controlled by the wait_on_process parameter. The API supports two processing modes, each suited for a different use case:

Asynchronous Processing

Returns immediately and processes the image in the background. Ideal for user-facing applications where immediate feedback is important. See the Asynchronous Processing Guide for complete implementation details, webhook configuration, and code examples.

Synchronous Processing

Waits for complete analysis before responding. Best for batch processing, server-to-server integrations, or when you can handle longer response times: Request:
{
  "body": "base64-encoded-image-data", 
  "analysis_mode": "fast",
  "wait_on_process": true
}
Response:
{
  "record_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
  "status": "completed",
  "dish_name": "grilled chicken caesar salad",
  "serving_size": 275,
  "unit": "g",
  "nutritional_fields": {
    "energy_kcal": 292,
    "protein_g": 42.2,
    "fat_total_g": 10.9,
    "carbohydrate_g": 4.3
  },
  "uploaded_at": "2025-09-15T10:30:04.521Z",
  "modified_at": "2025-09-15T10:30:12.132Z",
  "consumed_at": "2025-09-15T10:30:04Z"
}

Analysis Modes

  • precise (default): Uses advanced AI models for the highest accuracy and detailed analysis
  • fast: Uses optimized models for quicker processing with good accuracy

Localization

Provide country_code and language_code for region-specific analysis:
{
  "country_code": "de",
  "language_code": "de",
  "include_dish_description": true
}
Note, both codes should be lowercase. This enables:
  • Region-specific food recognition
  • Usage of local nutritional databases
  • Translated ingredient names and descriptions

Including Optional Data

Control what data is included in the analysis:
{
  "include_nutri_score": true,
  "include_dish_description": true,
  "include_ingredients": true,
  "include_nutrition_fields": [
    "energy_kcal",
    "protein_g", 
    "fat_total_g",
    "carbohydrate_g",
    "fiber_total_dietary_g",
    "sodium_mg",
    "calcium_mg",
    "iron_mg"
  ]
}

Selection of Nutritional Fields

You can control which nutritional fields are included in the analysis:
{
  "include_nutrition_fields": [
    "energy_kcal",
    "protein_g", 
    "fat_total_g",
    "carbohydrate_g",
    "fiber_total_dietary_g",
    "sodium_mg",
    "calcium_mg",
    "vitamin_c_mg"
  ]
}
Note: If include_nutrition_fields is omitted or empty, only four basic fields are included by default:
  • carbohydrate_g
  • energy_kcal
  • fat_total_g
  • protein_g

Response Body

Processing Status

Analysis of the nutrition record progresses through these states:
  • pending — Analysis has been queued
  • processing — an AI model is actively analyzing the image
  • completed — Analysis finished successfully with results
  • failed — Processing failed due to unidentifiable content or technical issues
REST API responses and webhook notifications will always include the status field.
{
  "record_id": "0135012d-a9f5-8bf6-885f-8badcaf8d203",
  "status": "processing",
  "uploaded_at": "2025-09-15T20:02:01.049Z",
  "modified_at": "2025-09-15T20:02:01.05Z",
  "consumed_at": "2025-09-15T20:02:01Z"
}
Webhook notifications can have status field with values completed or failed only. Always handle different response statuses in your application logic.

Error Scenarios

Processing Failures

These occur when the API successfully receives your request but the AI analysis fails. The response will have HTTP 200 status with "status": "failed". When status is failed, check the failure_reason field for specific details:
  • Unidentifiable food items — image contains non-food objects or unclear food items
      "status": "failed",
      "dish_name": "unidentifiable",
      "failure_reason": "no food items visible; image depicts landmark (Eiffel Tower)",
    
  • Poor image quality — blurry, dark, or low-resolution images
      "status": "failed",
      "dish_name": "unidentifiable",
      "failure_reason": "image too blurry to identify any food items clearly",
    
  • AI processing timeouts — the processing by AI providers sometimes might take longer than expected due to reasons that cannot be foreseen. To control it, the upload process flow will result in a timeout error. Once timeout occurs, the API will return HTTP status 200 and the body message with the status field set to failed:
    {
        "record_id": "013501f7-1ad0-8c25-82ae-fc843ead4135",
        "status": "failed",
        "failure_reason": "nutrition processing timed out",
        "uploaded_at": "2025-09-15T20:48:19.731Z",
        "modified_at": "2025-09-15T20:48:34.815Z",
        "consumed_at": "2025-09-15T20:48:19Z"
    }
    

Request Errors

These occur when there’s an issue with the request itself, returning non-200 HTTP status codes before analysis begins.
  • Decoding Image Error — if the image is corrupted or improperly encoded, and therefore cannot be decoded, then HTTP 400 will be returned.
    {
      "title": "Bad Request",
      "status": 400,
      "detail": "failed to base64 decode body"
    }
    
  • Invalid Image Format — nutrition AI supports JPEG, PNG, and WebP formats. When an image format is not supported, HTTP status code 400 will be returned.
    {
      "title": "Bad Request",
      "status": 400,
      "detail": "unsupported mime type: image/gif"
    }
    
  • Exceeding Image Size Limits — images larger than 10MB are not accepted, and API will return HTTP 413 status code.
    {
      "title": "Request Entity Too Large",
      "status": 413,
      "detail": "request body is too large limit=10485760 bytes"
    }
    
    Images larger than 11MB will be rejected by the gateway with HTTP 413 status code and the following error message.
    413 Request Entity Too Large
    
  • Invalid Image URLs — when image is provided with the body_url parameter and the URL path is not found at the host, HTTP status code 422 will be returned.
    {
        "title": "Unprocessable Entity",
        "status": 422,
        "detail": "failed to GET body_url: status 404"
    }
    
    If the body_url parameter value is not a proper HTTP(S) URL, the following error will be given.
    {
      "title": "Unprocessable Entity",
      "status": 422,
      "detail": "validation failed",
      "errors": [
        {
          "message": "body_url must be a valid HTTP(S) URL",
          "location": "body.body_url",
          "value": "example.com/image.jpeg"
        }
      ]
    }
    
For general API error handling, see the Error Handling documentation.

Nutrition Fields and Units

The names of nutrition fields are always lowercase and include units as suffix. Nutrition fields are not translated.

Retrieving Results

Getting Results

For asynchronous processing, use webhooks for real-time notifications. See the Asynchronous Processing Guide for complete webhook implementation details. If webhooks are not yet available, you can check status using the GET /nutrition_records/{id} endpoint. Retrospectively, a list of all analyzed nutrition records can be retrieved using the GET /nutrition_records endpoint by providing the time range.

Deleting Results

A result can be deleted using DETELE /nutrition_records/{id} endpoint by providing the record ID. An HTTP 204 status code will be returned if successful, regardless if the record existed or not.

Updating Results

You can update the serving size of any analyzed nutrition record. When the serving size is changed, all ingredients and their respective nutritional fields will be automatically recalculated proportionally to maintain the same nutritional ratios. This allows you to easily adjust portion sizes while maintaining accurate nutritional information. A result can be updated by calling PATCH /nutrition_records/{id} endpoint.

Best Practices

1. Choose the Right Processing Mode

  • Asynchronous: For user-facing applications, mobile apps, and when you want immediate feedback
  • Synchronous: For batch processing, server-to-server integrations, and when you can handle longer response times

2. Implement Proper Error Handling

  • Handle network errors gracefully
  • Provide meaningful error messages to users
  • Implement retry logic for transient failures

Code Examples

Here are complete implementation examples in multiple programming languages:
import os
import requests
import base64
import hmac
import hashlib
import json

SPIKE_API_BASE_URL = os.getenv("SPIKE_API_BASE_URL")
SPIKE_APP_HMAC_KEY = os.getenv("SPIKE_APP_HMAC_KEY")

try:
    SPIKE_APP_ID = int(os.getenv("SPIKE_APP_ID"))
except (ValueError, TypeError):
    raise ValueError("SPIKE_APP_ID must be an integer.")

# Implementation depends on your authentication method
# See: /api-docs/authentication for complete details
class SpikeAuth:
    def __init__(self, end_user_id):
        self.user_id = end_user_id
        self.app_id = SPIKE_APP_ID
        self.hmac_key = SPIKE_APP_HMAC_KEY
        self.base_url = SPIKE_API_BASE_URL
        self.hmac_signature = self._generate_hmac_signature()
        self.access_token = None

    def _generate_hmac_signature(self):
        h = hmac.new(self.hmac_key.encode('utf-8'), self.user_id.encode('utf-8'), hashlib.sha256)
        return h.hexdigest()


    def get_bearer_token(self) -> str:
        if self.access_token is not None:
            return self.access_token
        else:
            body = {
                "application_id": self.app_id,
                "application_user_id": self.user_id,
                "signature": self.hmac_signature
            }

            response = requests.post(
                f"{self.base_url}/auth/hmac",
                json=body,
                headers={"Content-Type": "application/json", "Accept": "application/json"},
                timeout=20
            )

            if response.status_code == 200:
                try:
                    print(f"Successfully obtained Bearer token for {self.user_id}")
                    self.access_token = response.json()["access_token"]
                    return self.access_token
                except (KeyError, json.JSONDecodeError) as e:
                    print(f"Failed to parse access token from successful response for {self.user_id}: {e}")
                raise requests.RequestException(f"Token parsing failed for {self.user_id}")
            else:
                raise Exception(f"Authentication failed for {self.user_id} with status {response.status_code}")


class NutritionAPI:
    def __init__(self, auth : SpikeAuth):
        self.spike_auth = auth


    def _create_auth_headers(self, method="GET") -> dict[str, str]:
        """Create authentication headers - see authentication docs for details"""
        try:
            token = self.spike_auth.get_bearer_token()
            req_headers = {
                "Authorization": f"Bearer {token}",
                "Accept": "application/json"
            }
            if method == "POST":
                req_headers["Content-Type"] = "application/json"
            return req_headers
        except requests.RequestException as auth_error:
            print(f"Failed to get bearer token for Nutrition API request header: {auth_error}")
        raise


    def analyze_food_image(self, image_path, **options):
        """Upload a food image for nutritional analysis"""
        # Read and encode image
        try:
            with open(image_path, 'rb') as image_file:
                image_data = base64.b64encode(image_file.read()).decode('utf-8')
        except FileNotFoundError:
            print(f"Image file not found: {image_path}")
            return None

        # Prepare the request body
        body = {
            "body": image_data,
            "analysis_mode": options.get("analysis_mode", "precise"),
            "country_code": options.get("country_code", "us"),
            "language_code": options.get("language_code", "en"),
            "include_ingredients": options.get("include_ingredients", False),
            "include_nutri_score": options.get("include_nutri_score", True),
            "include_dish_description": options.get("include_dish_description", True),
            "ignore_cache": options.get("ignore_cache", False),
            "include_nutrition_fields": options.get("include_nutrition_fields", [
                "energy_kcal", "protein_g", "fat_total_g", "carbohydrate_g",
                "fiber_total_dietary_g", "sodium_mg"
            ]),
            "wait_on_process": options.get("wait_on_process", False)
        }

        # Create request body and headers
        body_json = json.dumps(body)

        headers = self._create_auth_headers(method="POST")

        # Make request
        response = requests.post(
            f"{SPIKE_API_BASE_URL}/nutrition_records",
            headers=headers,
            data=body_json
        )
        if response.status_code == 200:
            return response.json()

        print(f"Response status: {response.status_code}")
        if response.status_code == 400:
            print(f"Bad request. Response: {response.text}")
        elif response.status_code == 401:
            print(f"Unauthorized for Nutrition API. Token expired or invalid. Response: {response.text}")
            self.spike_auth.access_token = None
        elif response.status_code == 404:
            print(f"Resource not found")
        elif response.status_code == 413:
            print(f"Encoded picture exceeds 10MB. Response: {response.text}")
        elif response.status_code == 422:
            print(f"Invalid parameters. Response: {response.text}")
        elif response.status_code == 500:
            print(f"Internal server error - contact Spike support. Response: {response.text}")
        return None

def print_result(res):
    if res is not None:
        print(f"Status: {res['status']}. Record ID: {res['record_id']}")
        if res['status'] == 'failed':
            print(f"Error: {res['failure_reason']}")
        elif res['status'] == 'completed':
            print(f"Dish: {res['dish_name']}")
            if 'dish_name_translated' in res.keys():
                print(f"Dish translated: {res['dish_name_translated']}")
            if 'dish_description' in res.keys():
                print(f"Description: {res['dish_description']}")
            if 'dish_description_translated' in res.keys():
                print(f"Description translated: {res['dish_description_translated']}")
            print(f"Serving size: {res['serving_size']} {res['unit']} ")
            if 'nutri_score' in res.keys():
                print(f"Nutri-Score: {res['nutri_score']}")
            if 'nutritional_fields' in res.keys():
                print("Nutritional fields:")
                for field in res['nutritional_fields']:
                    print(f"- {field}: {res['nutritional_fields'].get(field)}")
            if 'ingredients' in res.keys():
                print("Ingredients:")
                for ingredient in res.get('ingredients'):
                    print(f"- {ingredient['name']}: {ingredient['serving_size']}{ingredient['unit']}")
                    if 'nutritional_fields' in ingredient.keys():
                        for field in ingredient['nutritional_fields']:
                            print(f"  + {field}: {ingredient['nutritional_fields'].get(field)}")

# Example usage
user_id = "nutrition_test_user"
spike_auth = SpikeAuth(user_id)
api = NutritionAPI(spike_auth)

# Upload image asynchronously
print(f"Asynchronous analysis")
result = api.analyze_food_image(
    "/path/to/image.jpg.jpg",
    include_ingredients=True
)
print_result(result)

# For synchronous processing (wait for results)
print(f"\nSynchronous analysis")
result = api.analyze_food_image(
    "/path/to/image.jpg.jpg",
    wait_on_process=True,
    include_ingredients=True,
    language_code="de",
    country_code="de"
)
print_result(result)
For complete API specification, data types, and additional parameters, see the POST /nutrition_records API Reference.