About
The Spike SDK provides a convenient interface for the Nutrition AI API, allowing you to analyze food images directly from your React Native application. The SDK handles image encoding, API communication, and response parsing, making it easy to integrate nutritional analysis into your app.
All Spike SDK async methods return Promises. Use try-catch blocks or .catch() handlers for error handling. See Error Handling for details.
Key Features
- AI-Powered Analysis — advanced computer vision for food identification and nutritional calculations
- Flexible Processing — choose between synchronous (wait for results) or asynchronous (background) processing
- Base64 Support — submit images as base64-encoded strings
- Complete Record Management — retrieve, update, and delete nutrition records
Available Methods
| Method | Description |
|---|
analyzeNutrition({ imageBase64, consumedAt?, config? }) | Submit food image for synchronous processing and wait for the analysis results |
submitNutritionForAnalysis({ imageBase64, consumedAt?, config? }) | Submit food image for asynchronous processing and get record ID immediately for polling afterwards |
getNutritionRecords({ from, to }) | Retrieve nutrition records for a datetime range |
getNutritionRecord({ id }) | Get a specific nutrition record by ID |
updateNutritionRecordServingSize({ id, servingSize }) | Update serving size for a nutrition record |
deleteNutritionRecord({ id }) | Delete a nutrition record by ID |
Analyzing Food Images
Synchronous Processing
Use synchronous analysis when you want to wait for the complete nutritional analysis before proceeding. This is ideal for scenarios where you need immediate results and can display a loading indicator.
import {
NutritionRecordAnalysisMode,
NutritionalField
} from '@anthropic/spike-react-native-sdk';
// Capture image from camera or gallery and convert to base64
const imageBase64 = // ... base64-encoded image data
try {
const record = await spikeConnection.analyzeNutrition({
imageBase64: imageBase64,
consumedAt: new Date(),
config: {
analysisMode: NutritionRecordAnalysisMode.precise,
countryCode: 'us',
languageCode: 'en',
includeNutriScore: true,
includeDishDescription: true,
includeIngredients: true,
includeNutritionFields: [
NutritionalField.energyKcal,
NutritionalField.proteinG,
NutritionalField.fatTotalG,
NutritionalField.carbohydrateG
]
}
});
console.log(`Dish: ${record.dishName ?? 'Unknown'}`);
console.log(`Serving size: ${record.servingSize ?? 0} ${record.unit ?? 'g'}`);
console.log(`Calories: ${record.nutritionalFields?.['energy_kcal'] ?? 0}`);
} catch (error) {
console.error('Analysis failed:', error);
}
You can also call with minimal parameters (config is optional):
// Using defaults - only imageBase64 is required
const record = await spikeConnection.analyzeNutrition({
imageBase64: imageBase64
});
Processing Time: Synchronous processing takes some time depending on image complexity. Consider showing a loading indicator to users. If you see that the analysis is taking too long, the recommendation is to use asynchronous processing instead.
Asynchronous Processing
Use asynchronous processing when you want an immediate response without waiting for the analysis to complete. Record ID is returned.
The image is processed in the background, and you can retrieve results later by requesting nutrition analysis using the record ID or receive them via webhook.
try {
// Submit image for background processing
const recordId = await spikeConnection.submitNutritionForAnalysis({
imageBase64: imageBase64,
consumedAt: new Date(),
config: {
analysisMode: NutritionRecordAnalysisMode.fast,
includeIngredients: true
}
});
console.log(`Analysis started. Record ID: ${recordId}`);
// Optionally, poll for results later
// Your backend will also receive a webhook when analysis completes
} catch (error) {
console.error('Failed to submit:', error);
}
Retrieving Results Asynchronously
After submitting an image for asynchronous processing, you can retrieve the results using the record ID. Check the processing status for completion success.
import { NutritionRecordStatus } from '@anthropic/spike-react-native-sdk';
// Check the status and get results
const record = await spikeConnection.getNutritionRecord({ id: recordId });
if (record) {
switch (record.status) {
case NutritionRecordStatus.completed:
console.log(`Analysis complete: ${record.dishName ?? 'Unknown'}`);
break;
case NutritionRecordStatus.processing:
console.log('Still processing...');
break;
case NutritionRecordStatus.pending:
console.log('Queued for processing...');
break;
case NutritionRecordStatus.failed:
console.log(`Analysis failed: ${record.failureReason ?? 'Unknown error'}`);
break;
case NutritionRecordStatus.unknown:
console.log('Unknown status. Please update SDK.');
break;
}
}
For real-time notifications, configure webhooks in your admin console. Your backend will receive a webhook notification when the analysis completes. See Asynchronous Processing for webhook implementation details.
Configuration Options
Customize the analysis using NutritionalAnalysisConfig:
const config = {
// Analysis speed vs. precision
analysisMode: NutritionRecordAnalysisMode.precise, // 'precise' (default) or 'fast'
// Country ISO 3166-1 alpha-2 code in lowercase
countryCode: 'us',
// Language ISO 639-1 code in lowercase
languageCode: 'en',
// Include Nutri-Score rating (A-E)
includeNutriScore: true,
// Include dish description
includeDishDescription: true,
// Include detailed breakdown of ingredients
includeIngredients: true,
// Specify which nutritional fields to include
includeNutritionFields: [
NutritionalField.energyKcal,
NutritionalField.proteinG,
NutritionalField.fatTotalG,
NutritionalField.carbohydrateG,
NutritionalField.fiberTotalDietaryG,
NutritionalField.sodiumMg
]
};
const record = await spikeConnection.analyzeNutrition({
imageBase64: imageBase64,
consumedAt: new Date(),
config: config
});
NutritionalAnalysisConfig
interface NutritionalAnalysisConfig {
/** A preferred mode for the analysis. Default is 'precise'. */
analysisMode: NutritionRecordAnalysisMode | null;
/** Country ISO 3166-1 alpha-2 code in lowercase */
countryCode: string | null;
/** Language ISO 639-1 code in lowercase */
languageCode: string | null;
/** Include nutri-score label of the food. Default is false. */
includeNutriScore: boolean | null;
/** Include dish description of the food. Default is false. */
includeDishDescription: boolean | null;
/** Include ingredients of the food. Default is false. */
includeIngredients: boolean | null;
/**
* Include specific nutrition fields in the analysis report.
* By default, carbohydrate_g, energy_kcal, fat_total_g and protein_g will be included.
*/
includeNutritionFields: NutritionalField[] | null;
}
Analysis Modes
enum NutritionRecordAnalysisMode {
fast = "fast",
precise = "precise"
}
| Mode | Description |
|---|
precise | Uses advanced AI models for highest accuracy and detailed analysis (default) |
fast | Uses optimized models for quicker processing with good accuracy |
Default Nutritional Fields
If includeNutritionFields is not specified, only these basic fields are included:
energyKcal
proteinG
fatTotalG
carbohydrateG
See Nutritional Fields Reference for all available fields.
Managing Nutrition Records
List Records by Date Range
Retrieve all nutrition records within a specified date range:
const now = new Date();
const startDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); // 7 days ago
const endDate = now;
try {
const records = await spikeConnection.getNutritionRecords({
from: startDate,
to: endDate
});
for (const record of records) {
const consumedAt = record.consumedAt ?? 'Unknown date';
const size = record.servingSize ?? 0;
const unit = record.unit ?? 'g';
console.log(`${consumedAt}: ${record.dishName ?? 'Unknown'} - ${size}${unit}`);
}
} catch (error) {
console.error('Failed to fetch records:', error);
}
Get a Specific Record
Retrieve a single nutrition record by its ID:
try {
const record = await spikeConnection.getNutritionRecord({ id: recordId });
if (record) {
console.log(`Dish: ${record.dishName ?? 'Unknown'}`);
console.log(`Nutri-Score: ${record.nutriScore ?? 'N/A'}`);
// Access nutritional values
if (record.nutritionalFields?.['energy_kcal']) {
console.log(`Calories: ${record.nutritionalFields['energy_kcal']} kcal`);
}
// Access ingredients if included
record.ingredients?.forEach(ingredient => {
console.log(`- ${ingredient.name}: ${ingredient.servingSize}${ingredient.unit}`);
});
} else {
console.log('Record not found');
}
} catch (error) {
console.error('Failed to fetch record:', error);
}
Update Serving Size
Adjust the serving size of an existing record. All nutritional values are automatically recalculated proportionally:
try {
const updatedRecord = await spikeConnection.updateNutritionRecordServingSize({
id: recordId,
servingSize: 200.0 // New serving size in grams
});
console.log(`Updated serving size: ${updatedRecord.servingSize ?? 0}${updatedRecord.unit ?? 'g'}`);
console.log(`Recalculated calories: ${updatedRecord.nutritionalFields?.['energy_kcal'] ?? 0}`);
} catch (error) {
console.error('Failed to update record:', error);
}
Delete a Record
Permanently remove a nutrition record (success status is returned regardless record is found or not):
try {
await spikeConnection.deleteNutritionRecord({ id: recordId });
console.log('Record deleted successfully');
} catch (error) {
console.error('Failed to delete record:', error);
}
Response Data
NutritionRecord
The NutritionRecord interface contains the analysis results:
interface NutritionRecord {
/** Report record ID */
recordId: UUID;
/** Processing status */
status: NutritionRecordStatus;
/** Detected dish name */
dishName: string | null;
/** Detected dish description */
dishDescription: string | null;
/** Dish name translated to target language */
dishNameTranslated: string | null;
/** Dish description translated to target language */
dishDescriptionTranslated: string | null;
/** Nutri-Score known as the 5-Colour Nutrition label (A-E) */
nutriScore: string | null;
/** Reason for processing failure */
failureReason: string | null;
/** Serving size in metric units */
servingSize: number | null;
/** Metric unit (g for solids, ml for liquids) */
unit: NutritionalUnit | null;
/** Nutritional values as key-value pairs */
nutritionalFields: { [key: string]: number } | null;
/** List of detected ingredients with nutritional information */
ingredients: NutritionRecordIngredient[] | null;
/** Upload timestamp in UTC (ISO 8601) */
uploadedAt: string;
/** Update timestamp in UTC (ISO 8601) */
modifiedAt: string;
/** The UTC time when food was consumed (ISO 8601) */
consumedAt: string | null;
}
NutritionRecordStatus
enum NutritionRecordStatus {
pending = "pending",
processing = "processing",
completed = "completed",
failed = "failed",
unknown = "_unknown"
}
NutritionalUnit
enum NutritionalUnit {
g = "g", // grams
mg = "mg", // milligrams
mcg = "mcg", // micrograms
ml = "ml", // milliliters
kcal = "kcal", // kilocalories
unknown = "_unknown"
}
NutritionRecordIngredient
interface NutritionRecordIngredient {
/** Ingredient name using LANGUAL standard terminology */
name: string;
/** Ingredient name translated to target language */
nameTranslated: string | null;
/** Serving size in metric units */
servingSize: number;
/** Metric unit (g for solids, ml for liquids) */
unit: NutritionalUnit;
/** Nutritional values as key-value pairs */
nutritionalFields: { [key: string]: number } | null;
}
NutritionalField
Use this enum to specify which nutritional fields to include in the analysis:
enum NutritionalField {
energyKcal = "energy_kcal",
carbohydrateG = "carbohydrate_g",
proteinG = "protein_g",
fatTotalG = "fat_total_g",
fatSaturatedG = "fat_saturated_g",
fatPolyunsaturatedG = "fat_polyunsaturated_g",
fatMonounsaturatedG = "fat_monounsaturated_g",
fatTransG = "fat_trans_g",
fiberTotalDietaryG = "fiber_total_dietary_g",
sugarsTotalG = "sugars_total_g",
cholesterolMg = "cholesterol_mg",
sodiumMg = "sodium_mg",
potassiumMg = "potassium_mg",
calciumMg = "calcium_mg",
ironMg = "iron_mg",
magnesiumMg = "magnesium_mg",
phosphorusMg = "phosphorus_mg",
zincMg = "zinc_mg",
vitaminARaeMcg = "vitamin_a_rae_mcg",
vitaminCMg = "vitamin_c_mg",
vitaminDMcg = "vitamin_d_mcg",
vitaminEMg = "vitamin_e_mg",
vitaminKMcg = "vitamin_k_mcg",
thiaminMg = "thiamin_mg",
riboflavinMg = "riboflavin_mg",
niacinMg = "niacin_mg",
vitaminB6Mg = "vitamin_b6_mg",
folateMcg = "folate_mcg",
vitaminB12Mcg = "vitamin_b12_mcg"
}
Error Handling
All nutrition methods return Promises that can reject with errors. Always use try-catch blocks or .catch() handlers:
try {
const record = await spikeConnection.analyzeNutrition({
imageBase64: imageBase64,
consumedAt: new Date()
});
// Handle success
} catch (error) {
if (error.code === 'INVALID_IMAGE') {
console.error('Invalid image format or size');
} else if (error.code === 'NETWORK_ERROR') {
console.error('Network error:', error.message);
} else if (error.code === 'UNAUTHORIZED') {
console.error('Authentication failed');
} else {
console.error('Error:', error.message);
}
}
Common Error Scenarios
| Error | Cause |
|---|
| Invalid image format | Image is not JPEG, PNG, or WebP |
| Image too large | Base64-encoded image exceeds 10MB |
| Image too small | Image is smaller than 512×512 pixels |
| Unauthorized | Invalid or expired authentication token |
| Analysis timeout | AI processing took too long |
| Unidentifiable | Non-food image |
Image Guidelines
For optimal analysis results, guide your users to capture images that:
- Center the food — capture the plate contents as the main subject
- Fill the frame — ensure the meal occupies most of the image
- Use proper lighting — natural or bright lighting works best
- Avoid obstructions — remove packaging and minimize utensils in frame
- Skip filters — avoid filters that alter the food’s appearance
See Image Guidelines for complete recommendations.
Best Practices
1. Request Only What You Need
Each additional field, ingredient breakdown, or optional data increases processing time. Only request what your app actually uses:
// ❌ Don't request everything "just in case"
const config = {
includeIngredients: true,
includeNutriScore: true,
includeDishDescription: true,
includeNutritionFields: Object.values(NutritionalField) // All 29 fields
};
// ✅ Request only what you need
const config = {
includeNutritionFields: [
NutritionalField.energyKcal,
NutritionalField.proteinG,
NutritionalField.carbohydrateG,
NutritionalField.fatTotalG
]
};
2. Consider your actual UI requirements:
- Do you display ingredients? If not, skip
includeIngredients.
- Do you show Nutri-Score? If not, skip
includeNutriScore.
- Which nutritional values do you actually display? Request only those.
3. Choose the Right Processing Mode
- Synchronous (
analyzeNutrition): Use when you need immediate results and can show a loading state
- Asynchronous (
submitNutritionForAnalysis): Use for better UX when you don’t need immediate results, or when processing multiple images
4. Handle All Status Values
When using asynchronous processing, always check the record status before accessing results:
const record = await spikeConnection.getNutritionRecord({ id: recordId });
if (!record) {
// Handle not found
return;
}
if (record.status !== NutritionRecordStatus.completed) {
if (record.status === NutritionRecordStatus.failed) {
// Handle failure
console.error(`Failed: ${record.failureReason}`);
} else {
// Still processing
console.log(`Status: ${record.status}`);
}
return;
}
// Safe to access results
console.log(`Dish: ${record.dishName}`);
5. Implement Webhook Handling
For production apps using asynchronous processing, implement webhook handling on your backend to receive real-time notifications when analysis completes.
6. Create Reusable Configuration
If you’re using the same settings across your app, create a shared configuration helper:
// nutritionConfig.js
import { NutritionRecordAnalysisMode, NutritionalField } from '@anthropic/spike-react-native-sdk';
export const standardNutritionConfig = {
analysisMode: NutritionRecordAnalysisMode.precise,
includeIngredients: true,
includeNutriScore: true,
includeNutritionFields: [
NutritionalField.energyKcal,
NutritionalField.proteinG,
NutritionalField.fatTotalG,
NutritionalField.carbohydrateG,
NutritionalField.fiberTotalDietaryG
]
};
// Usage
import { standardNutritionConfig } from './nutritionConfig';
const record = await spikeConnection.analyzeNutrition({
imageBase64: imageBase64,
consumedAt: new Date(),
config: standardNutritionConfig
});
7. Use React State for Loading UI
import React, { useState } from 'react';
import { View, ActivityIndicator, Text } from 'react-native';
import { NutritionalField } from '@anthropic/spike-react-native-sdk';
function NutritionAnalyzer({ spikeConnection }) {
const [isLoading, setIsLoading] = useState(false);
const [result, setResult] = useState(null);
const [error, setError] = useState(null);
const analyzeFood = async (imageBase64) => {
setIsLoading(true);
setError(null);
try {
const record = await spikeConnection.analyzeNutrition({
imageBase64: imageBase64,
consumedAt: new Date(),
config: {
includeNutritionFields: [
NutritionalField.energyKcal,
NutritionalField.proteinG
]
}
});
setResult(record);
} catch (err) {
setError(err.message);
} finally {
setIsLoading(false);
}
};
if (isLoading) {
return (
<View>
<ActivityIndicator size="large" />
<Text>Analyzing your meal...</Text>
</View>
);
}
// Render result or error...
}