> ## Documentation Index
> Fetch the complete documentation index at: https://docs.spikeapi.com/llms.txt
> Use this file to discover all available pages before exploring further.

# iOS SDK Nutrition AI

> Analyze food images and retrieve nutritional information using AI-powered analysis in your iOS app.

## About

The Spike SDK provides a convenient interface for the [Nutrition AI API](/nutrition-ai/overview), allowing you to analyze food images directly from your iOS application. The SDK handles image encoding, API communication, and response parsing, making it easy to integrate nutritional analysis into your app.

<Note>
  All Spike SDK async method calls should be wrapped in a `do-catch` block. See [Error Handling](#error-handling) for details.
</Note>

## 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
* **`UIImage` Support** — convenient methods that accept `UIImage` directly, in addition to base64-encoded strings
* **Complete Record Management** — retrieve, update, and delete nutrition records

## Available Methods

| Method                                                 | Description                                                                                        |
| ------------------------------------------------------ | -------------------------------------------------------------------------------------------------- |
| `analyzeNutrition(image:consumedAt:config:)`           | Submit food image for synchronous processing and wait for the analysis results                     |
| `submitNutritionForAnalysis(image: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.

```swift theme={null}
import SpikeSDK

let image: UIImage = // ... captured from camera or photo library

do {
    let record = try await spikeConnection.analyzeNutrition(
        image: image,
        consumedAt: Date(),
        config: NutritionalAnalysisConfig(
            analysisMode: .precise,
            includeIngredients: true,
            includeNutriScore: true
        )
    )
    
    print("Dish: \(record.dishName ?? "Unknown")")
    print("Serving size: \(record.servingSize ?? 0) \(record.unit?.rawValue ?? "g")")
    print("Calories: \(record.nutritionalFields?["energy_kcal"] ?? 0)")
} catch {
    print("Analysis failed: \(error)")
}
```

You can also use base64-encoded image data:

```swift theme={null}
// Using base64-encoded string
let imageData = image.jpegData(compressionQuality: 0.8)!
let base64String = imageData.base64EncodedString()

let record = try await spikeConnection.analyzeNutrition(
    imageBase64: base64String,
    consumedAt: Date(),
    config: nil  // Uses default configuration
)
```

<Note>
  **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.
</Note>

### 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.

```swift theme={null}
do {
    // Submit image for background processing
    let recordId = try await spikeConnection.submitNutritionForAnalysis(
        image: image,
        consumedAt: Date(),
        config: NutritionalAnalysisConfig(
            analysisMode: .fast,
            includeIngredients: true
        )
    )
    
    print("Analysis started. Record ID: \(recordId)")
    
    // Optionally, poll for results later
    // Your backend will also receive a webhook when analysis completes
    
} catch {
    print("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.

```swift theme={null}
// Check the status and get results
if let record = try await spikeConnection.getNutritionRecord(id: recordId) {
    switch record.status {
    case .completed:
        print("Analysis complete: \(record.dishName ?? "Unknown")")
    case .processing:
        print("Still processing...")
    case .pending:
        print("Queued for processing...")
    case .failed:
        print("Analysis failed: \(record.failureReason ?? "Unknown error")")
    case .unknown:
        print("Unknown status. Please update SDK.")
    }
}
```

<Tip>
  For real-time notifications, configure webhooks in your [admin console](https://admin.spikeapi.com/). Your backend will receive a webhook notification when the analysis completes. See [Asynchronous Processing](/nutrition-ai/async) for webhook implementation details.
</Tip>

## Configuration Options

Customize the analysis using `NutritionalAnalysisConfig`:

```swift theme={null}
let config = NutritionalAnalysisConfig(
    // Analysis speed vs. precision
    analysisMode: .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 (using NutritionalField enum)
    includeNutritionFields: [
        .energyKcal,
        .proteinG,
        .fatTotalG,
        .carbohydrateG,
        .fiberTotalDietaryG,
        .sodiumMg
    ]
)
```

### `NutritionalAnalysisConfig`

```swift theme={null}
public struct NutritionalAnalysisConfig: Codable, Hashable, Sendable {
    /// A preferred mode for the analysis. Default is ".precise".
    public var analysisMode: NutritionRecordAnalysisMode?
    /// Country ISO 3166-1 alpha-2 code in lowercase
    public var countryCode: String?
    /// Language ISO 639-1 code in lowercase
    public var languageCode: String?
    /// Include nutri-score label of the food. Default is false.
    public var includeNutriScore: Bool?
    /// Include dish description of the food. Default is false.
    public var includeDishDescription: Bool?
    /// Include ingredients of the food. Default is false.
    public var includeIngredients: Bool?
    /// Include specific nutrition fields in the analysis report.
    /// By default, carbohydrate_g, energy_kcal, fat_total_g and protein_g will be included.
    public var includeNutritionFields: [NutritionalField]?
}
```

### Analysis Modes

```swift theme={null}
public enum NutritionRecordAnalysisMode: String, Codable, Hashable, Sendable, CaseIterable {
    case fast
    case 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](/technical-references/nutritional_fields) for all available fields or check the [API Reference](https://spike_api.gitlab.io/spike-ios-sdk/documentation/spikesdk/nutritionalfield) for Swift enum values.

## Managing Nutrition Records

### List Records by Date Range

Retrieve all nutrition records within a specified date range:

```swift theme={null}
let startDate = Calendar.current.date(byAdding: .day, value: -7, to: Date())!
let endDate = Date()

do {
    let records = try await spikeConnection.getNutritionRecords(
        from: startDate,
        to: endDate
    )
    
    for record in records {
        let consumedAt = record.consumedAt?.description ?? "Unknown date"
        let size = record.servingSize ?? 0
        let unit = record.unit?.rawValue ?? "g"
        print("\(consumedAt): \(record.dishName ?? "Unknown") - \(size)\(unit)")
    }
} catch {
    print("Failed to fetch records: \(error)")
}
```

### Get a Specific Record

Retrieve a single nutrition record by its ID:

```swift theme={null}
do {
    if let record = try await spikeConnection.getNutritionRecord(id: recordId) {
        print("Dish: \(record.dishName ?? "Unknown")")
        print("Nutri-Score: \(record.nutriScore ?? "N/A")")
        
        // Access nutritional values
        if let calories = record.nutritionalFields?["energy_kcal"] {
            print("Calories: \(calories) kcal")
        }
        
        // Access ingredients if included
        for ingredient in record.ingredients ?? [] {
            print("- \(ingredient.name): \(ingredient.servingSize)\(ingredient.unit.rawValue)")
        }
    } else {
        print("Record not found")
    }
} catch {
    print("Failed to fetch record: \(error)")
}
```

### Update Serving Size

Adjust the serving size of an existing record. All nutritional values are automatically recalculated proportionally:

```swift theme={null}
do {
    let updatedRecord = try await spikeConnection.updateNutritionRecordServingSize(
        id: recordId,
        servingSize: 200.0  // New serving size in grams
    )
    
    print("Updated serving size: \(updatedRecord.servingSize ?? 0)\(updatedRecord.unit?.rawValue ?? "g")")
    print("Recalculated calories: \(updatedRecord.nutritionalFields?["energy_kcal"] ?? 0)")
} catch {
    print("Failed to update record: \(error)")
}
```

### Delete a Record

Permanently remove a nutrition record (success status is returned regardless record is found or not):

```swift theme={null}
do {
    try await spikeConnection.deleteNutritionRecord(id: recordId)
    print("Record deleted successfully")
} catch {
    print("Failed to delete record: \(error)")
}
```

## Response Data

### `NutritionRecord`

The `NutritionRecord` structure contains the analysis results:

```swift theme={null}
public struct NutritionRecord: Codable, Hashable, Sendable {
    /// Report record ID
    public var recordId: UUID
    /// Processing status
    public var status: NutritionRecordStatus
    /// Detected dish name
    public var dishName: String?
    /// Detected dish description
    public var dishDescription: String?
    /// Dish name translated to target language
    public var dishNameTranslated: String?
    /// Dish description translated to target language
    public var dishDescriptionTranslated: String?
    /// Nutri-Score known as the 5-Colour Nutrition label (A-E)
    public var nutriScore: String?
    /// Reason for processing failure
    public var failureReason: String?
    /// Serving size in metric units
    public var servingSize: Double?
    /// Metric unit (g for solids, ml for liquids)
    public var unit: NutritionalUnit?
    public var nutritionalFields: [String: Double]?
    /// List of detected ingredients with nutritional information
    public var ingredients: [NutritionRecordIngredient]?
    /// Upload timestamp in UTC
    public var uploadedAt: Date
    /// Update timestamp in UTC
    public var modifiedAt: Date
    /// The UTC time when food was consumed
    public var consumedAt: Date?
}
```

### `NutritionRecordStatus`

```swift theme={null}
public enum NutritionRecordStatus: String, Codable, Hashable, Sendable, CaseIterable {
    case pending
    case processing
    case completed
    case failed
    /// Unknown value was sent from API. SDK should be updated to use the newest API responses.
    case unknown = "_unknown"
}
```

### `NutritionalUnit`

```swift theme={null}
public enum NutritionalUnit: String, Codable, Hashable, Sendable, CaseIterable {
    case g      // grams
    case mg     // milligrams
    case mcg    // micrograms
    case ml     // milliliters
    case kcal   // kilocalories
    /// Unknown value was sent from API. SDK should be updated to use the newest API responses.
    case unknown = "_unknown"
}
```

### `NutritionRecordIngredient`

```swift theme={null}
public struct NutritionRecordIngredient: Codable, Hashable, Sendable {
    /// Ingredient name using LANGUAL standard terminology
    public var name: String
    /// Ingredient name translated to target language
    public var nameTranslated: String?
    /// Serving size in metric units
    public var servingSize: Double
    /// Metric unit (g for solids, ml for liquids)
    public var unit: NutritionalUnit
    public var nutritionalFields: [String: Double]?
}
```

### `NutritionalField`

Use this enum to specify which nutritional fields to include in the analysis:

```swift theme={null}
public enum NutritionalField: String, Codable, Hashable, Sendable, CaseIterable {
    case energyKcal = "energy_kcal"
    case carbohydrateG = "carbohydrate_g"
    case proteinG = "protein_g"
    case fatTotalG = "fat_total_g"
    case fatSaturatedG = "fat_saturated_g"
    case fatPolyunsaturatedG = "fat_polyunsaturated_g"
    case fatMonounsaturatedG = "fat_monounsaturated_g"
    case fatTransG = "fat_trans_g"
    case fiberTotalDietaryG = "fiber_total_dietary_g"
    case sugarsTotalG = "sugars_total_g"
    case cholesterolMg = "cholesterol_mg"
    case sodiumMg = "sodium_mg"
    case potassiumMg = "potassium_mg"
    case calciumMg = "calcium_mg"
    case ironMg = "iron_mg"
    case magnesiumMg = "magnesium_mg"
    case phosphorusMg = "phosphorus_mg"
    case zincMg = "zinc_mg"
    case vitaminARaeMcg = "vitamin_a_rae_mcg"
    case vitaminCMg = "vitamin_c_mg"
    case vitaminDMcg = "vitamin_d_mcg"
    case vitaminEMg = "vitamin_e_mg"
    case vitaminKMcg = "vitamin_k_mcg"
    case thiaminMg = "thiamin_mg"
    case riboflavinMg = "riboflavin_mg"
    case niacinMg = "niacin_mg"
    case vitaminB6Mg = "vitamin_b6_mg"
    case folateMcg = "folate_mcg"
    case vitaminB12Mcg = "vitamin_b12_mcg"
}
```

## Error Handling

All nutrition methods can throw errors. Always wrap calls in `do-catch` blocks:

```swift theme={null}
do {
    let record = try await spikeConnection.analyzeNutrition(
        image: image,
        consumedAt: Date(),
        config: nil
    )
    // Handle success
} catch let error as SpikeError {
    switch error {
    case .invalidImage:
        print("Invalid image format or size")
    case .networkError(let underlying):
        print("Network error: \(underlying)")
    case .serverError(let message):
        print("Server error: \(message)")
    case .unauthorized:
        print("Authentication failed")
    default:
        print("Error: \(error)")
    }
} catch {
    print("Unexpected error: \(error)")
}
```

### 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:

1. **Center the food** — capture the plate contents as the main subject
2. **Fill the frame** — ensure the meal occupies most of the image
3. **Use proper lighting** — natural or bright lighting works best
4. **Avoid obstructions** — remove packaging and minimize utensils in frame
5. **Skip filters** — avoid filters that alter the food's appearance

See [Image Guidelines](/nutrition-ai/overview#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:

```swift theme={null}
// ❌ Don't request everything "just in case"
let config = NutritionalAnalysisConfig(
    includeIngredients: true,
    includeNutriScore: true,
    includeDishDescription: true,
    includeNutritionFields: NutritionalField.allCases  // All 29 fields
)

// ✅ Request only what you need
let config = NutritionalAnalysisConfig(
    includeNutritionFields: [.energyKcal, .proteinG, .carbohydrateG, .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:

```swift theme={null}
guard record.status == .completed else {
    if record.status == .failed {
        // Handle failure
    } else {
        // Still processing
    }
    return
}
// Safe to access results
```

### 5. Implement Webhook Handling

For production apps using asynchronous processing, implement [webhook handling](/nutrition-ai/async) on your backend to receive real-time notifications when analysis completes.

### 6. Cache Configuration

Create a shared configuration object if you're using the same settings across your app:

```swift theme={null}
extension NutritionalAnalysisConfig {
    static let standard = NutritionalAnalysisConfig(
        analysisMode: .precise,
        includeIngredients: true,
        includeNutriScore: true,
        includeNutritionFields: [
            .energyKcal, .proteinG, .fatTotalG, 
            .carbohydrateG, .fiberTotalDietaryG
        ]
    )
}

// Usage
let record = try await spikeConnection.analyzeNutrition(
    image: image,
    consumedAt: Date(),
    config: .standard
)
```

## Related Documentation

* [Nutrition AI Overview](/nutrition-ai/overview) — API overview and key features
* [Implementation Guide](/nutrition-ai/implementation) — Detailed API implementation patterns
* [Asynchronous Processing](/nutrition-ai/async) — Webhook configuration and handling
* [Nutritional Fields Reference](/technical-references/nutritional_fields) — Complete list of available nutritional fields
