# Authentication Source: https://docs.spikeapi.com/api-docs/authentication Authentication process Spike API authenticates end users using **HMAC signatures** generated with a **shared secret key**. You can get this key from the **administrative console**. ## Step 1: Generate an HMAC Signature Use the secret key from your console to generate an HMAC-SHA256 signature for each user. hmac key ### Code Examples ```python [Python] theme={null} import hmac import hashlib # Example: sign_user("my_application_user_123") def sign_user(user_id: str) -> str: hmac_key = b"HMAC_KEY_FROM_ADMIN_CONSOLE" h = hmac.new(hmac_key, user_id.encode(), hashlib.sha256) return h.hexdigest() ``` ```javascript [JavaScript] theme={null} const crypto = require("crypto"); // Example: signUser("my_application_user_123") function signUser(userId) { const hmacKey = "HMAC_KEY_FROM_ADMIN_CONSOLE"; const hmac = crypto.createHmac("sha256", hmacKey); hmac.update(userId); return hmac.digest("hex"); } ``` ```go [Go] theme={null} import ( "crypto/hmac" "crypto/sha256" "encoding/hex" ) // Example: signUser("my_application_user_123") func signUser(userID string) (string, error) { m := hmac.New(sha256.New, []byte("HMAC_KEY_FROM_ADMIN_CONSOLE")) _, err := m.Write([]byte(userID)) if err != nil { return "", err } return hex.EncodeToString(m.Sum(nil)), nil } ``` ```php [PHP] theme={null} ## Step 2: Exchange Signature for Access Token ```mermaid theme={null} sequenceDiagram participant A as Your App participant S as Spike API Note over A: Generate HMAC-SHA256
signature using user ID
and shared secret key A->>+S: POST /auth/hmac S-->>-A: 200 OK - Access Token (JWT) Note over A: Store token securely
for subsequent API calls ``` Send the generated signature to the following endpoint to authorize the user and get an access token: `https://app-api.spikeapi.com/v3/auth/hmac` ### Request Parameters The ID of the application for which you are requesting the access token. The unique ID of the user in your system. This is the only identifier needed to reference the user. Maximum 1-128 alphanumeric characters. May include these special characters: - \_ . The HMAC signature generated to verify the user's authenticity. #### Example Request ```bash theme={null} curl --location 'https://app-api.spikeapi.com/v3/auth/hmac' \ --header 'Content-Type: application/json' \ --header 'Accept: application/json' \ --data '{ "application_id": 9999, "application_user_id": "my_application_user_123", "signature": "SIGNATURE_FROM_STEP_1" }' ``` ### Response The access token for the user. Must be consistent throughout the user's lifecycle. No pre-registration is required—users are valid after the first provider integration. #### Example Response ```json theme={null} { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI5OTk5Iiwic3ViIjoiYXBwbGljYXRpb24tdXNlci1pZC0xMjMifQ.XnI1y4tkRjdiSeHwUqdmk9em-hTPojtMzbOU30nMd_Y" } ``` ## Step 3: Store and Use the Access Token On successful authentication, the API will return an **access token** in the response. Store this token securely and include it in the Authorization header for all subsequent API calls: `Authorization: Bearer ` # Configuration Source: https://docs.spikeapi.com/api-docs/configuration Essential application settings for API integration Configure your Spike API application through the [admin console](https://admin.spikeapi.com/). ## Credentials Access your application credentials from the admin console to authenticate and secure your API integration. Application Credentials ### Application ID **Generated by Spike API** — Your unique application identifier used in all API requests. Required for [authentication](/api-docs/authentication) and identifying your app in API calls. Visible in admin console. ### HMAC Key **Generated by Spike API** — Required for user authentication. Use this key to generate HMAC-SHA256 signatures of user IDs in your [authentication flow](/api-docs/authentication). Found in the admin console under your application settings. ### Webhook Signature Key **Generated by Spike API** — Required for webhook security. Use this key to verify that [webhook](/api-docs/webhooks) requests genuinely come from Spike API by validating the `X-Body-Signature` header. Found in admin console under your application settings. ## Integration Settings Configure your application settings through the admin console to customize integration behavior and data handling. Application Configuration ### Default Redirect URL **Configured by you** — Fallback URL when users complete [provider integration](/api-docs/provider_integration) without a specific redirect URL. Use this to handle successful connections and show integration status to users. Set in admin console. **Placeholders:** * `{application_user_id}` - Your user ID * `{provider_slug}` - Provider name (e.g., "garmin", "fitbit") * `{provider_user_id}` - Provider's user ID **Auto-appended parameters:** * `user_id={application_user_id}` * `provider_slug={provider_slug}` * `error={error_text}` (on failure) ### Allowed Redirect Domains **Configured by you** — Security whitelist that prevents redirect attacks. Required when you want to use dynamic redirect URLs in your [provider integration](/api-docs/provider_integration) requests (e.g., mobile deep links, different app sections). Set in admin console. ### Main Webhook URL **Configured by you** — Receive real-time notifications when user data changes. Essential for keeping your app synchronized with health data updates from providers like Garmin, Fitbit, etc. Set in admin console. Must respond with HTTP 200 within 30 seconds. ### Max Backfill (days) **Configured by you** — Controls how much historical data to fetch when users first connect a provider. Higher values give more historical context but increase processing time and API usage. Set in admin console. **Limits:** Max 90 days, cannot exceed Dataset Retention Policy. **Provider limits:** See [Provider limits](/technical-references/application_configuration#data-backfill-configuration) for more details. SDK providers require manual extraction. See [SDK backfill docs](/sdk-docs/ios/backfill). ## Complete Reference See [Application Configuration](/technical-references/application_configuration) for detailed settings and security practices. # Errors Source: https://docs.spikeapi.com/api-docs/errors Handling of REST API error responses # Error Responses The API uses [RFC 9457 Problem Details for HTTP APIs](https://datatracker.ietf.org/doc/html/rfc9457) for error responses, with content type `application/problem+json`. All error responses follow a consistent structure designed to provide clear, actionable information about what went wrong. ## Error Structure All error responses use the following structure: ```json theme={null} { "title": "Error Title", "status": 400, "detail": "Human-readable explanation of the problem", "errors": [ { "location": "body.field_name", "message": "Specific error message", "value": "problematic_value" } ] } ``` ### Fields * **`title`**: A short, human-readable summary of the problem type * **`status`**: The HTTP status code * **`detail`**: A human-readable explanation specific to this occurrence of the problem * **`errors`** *(optional)*: An array of detailed error information, including: * **`location`**: Where the error occurred (e.g., `body.items[3].tags` or `path.user-id`) * **`message`**: Specific error message text * **`value`**: The problematic value that caused the error ## Common Error Codes ### 400 Bad Request Returned when the request cannot be processed due to malformed syntax, invalid JSON, or other client errors. ```json theme={null} { "title": "Bad Request", "status": 400, "detail": "validation failed", "errors": [ { "message": "invalid character ',' looking for beginning of value", "location": "body", "value": "{\n \"field_name\":,\n \"another_field\": true\n}" } ] } ``` ### 401 Unauthorized Returned when authentication is required but has failed or not been provided. The API supports multiple authentication methods, and different failure modes will return specific error messages. ```json theme={null} { "title": "Unauthorized", "status": 401, "detail": "Unauthorized: invalid authorization header", "errors": [ { "message": "invalid authorization header", "location": "authorization" } ] } ``` #### Common 401 Reasons * **`malformed authorization header`**: The `Authorization` header doesn't follow the expected `Bearer ` format * **`invalid authorization header`**: The provided token is invalid, expired, or cannot be parsed * **`expired jwt`**: The JWT token has passed its expiration time and is no longer valid * **`application not found`**: The application referenced in the token doesn't exist * **`application not active`**: The application exists but is not in an active state ### 403 Forbidden Returned when the authenticated user or application lacks permission to access the requested resource or perform the requested action. ```json theme={null} { "title": "Forbidden", "status": 403, "detail": "Nutrition Records are not enabled for this application" } ``` ### 413 Request Entity Too Large Returned when the request payload exceeds the maximum allowed size. The API limits request payloads to **10 MB**. ```json theme={null} { "title": "Request Entity Too Large", "status": 413, "detail": "request body is too large limit=10485760 bytes" } ``` ### 422 Unprocessable Entity Returned when the request is syntactically correct but contains validation errors. This includes schema validation failures, business rule violations, and data consistency issues. ```json theme={null} { "title": "Unprocessable Entity", "status": 422, "detail": "validation failed", "errors": [ { "message": "expected value to be one of \"option1, option2, option3\"", "location": "body.field_name", "value": "invalid_option" }, { "message": "field is required", "location": "body.required_field" } ] } ``` ### 500 Internal Server Error Returned when an unexpected server-side error occurs. In production environments, sensitive error details are encrypted and replaced with a reference code that can be shared with support. ```json theme={null} { "title": "Internal Server Error", "status": 500, "detail": "Internal error at 1754296226: Share the following reference code with support: o8XIyE2aubQXVvgbiK6jDRjmJAdZRl9na1PBOm7tq_5Vi0o53ZHMzBc9JxUQfuNHcSLt7jEXnrMIrxRjrSFQBC9-BfwEVPEc5JqNueh9egsodrxmCsnzoD6EaKJoH2QbEup_pBuVQZNQIRzfssluj3jbuLmdCNnBJ86ygiM_V0pvNnPBsrNWXZj1uQ" } ``` **Note**: When you encounter a 500 error, please share the reference code with our support team. This encrypted reference contains the technical details needed to diagnose and resolve the issue while protecting sensitive system information. ## Error Handling Best Practices 1. **Check the `status` field** to determine the error category 2. **Read the `detail` field** for a human-readable explanation 3. **Process the `errors` array** for specific validation failures 4. **Use the `location` field** to identify which input fields need correction 5. **For 400 errors**, review the `errors` array to identify the specific field that caused the error 6. **For 401 errors**, check your authentication credentials and token validity 7. **For 413 errors**, reduce your payload size to under 10 MB 8. **For 422 errors**, review each item in the `errors` array to fix validation issues 9. **For 500 errors**, save the reference code and contact support ## Exhaustive Error Reporting The API strives to return exhaustive errors whenever possible to prevent frustration from repeated failed requests. When multiple validation errors occur, all detected issues are included in a single response rather than failing on the first error encountered. # API Overview Source: https://docs.spikeapi.com/api-docs/overview Spike API is designed for developers who want to seamlessly connect their applications to user health data from multiple providers. Below is a high-level guide to the main parts of the integration process. Each section links to detailed documentation. ## Application Configuration Configure your Spike API application settings, including authentication keys, webhook URLs, data retention policies, and backfill settings through the admin console. [Read the Configuration documentation →](./configuration) ## Authentication Securely authenticate your users using HMAC signatures and access tokens. This step ensures that only authorized users and applications can access sensitive health data. [Read the Authentication documentation →](./authentication) ## Provider Integration Connect your users to health data providers (such as Garmin or Fitbit) and manage consent flows. This enables your application to access user data with proper authorization. [Read the Provider Integration documentation →](./provider_integration) ## Webhooks Receive real-time updates when user data changes or new data is available. Set up webhook endpoints to automate data ingestion and stay in sync with provider updates. [Read the Webhooks documentation →](./webhooks) ## Querying Data Retrieve user health data using flexible query options tailored to your application's needs. Choose the right query method for statistics, sleep, workouts, samples, or provider records. [Read the Query Options Comparison documentation →](./query_options_comparison) # Provider Integration Source: https://docs.spikeapi.com/api-docs/provider_integration Integration with providers Most providers (e.g., Garmin, Fitbit) require the user to grant consent before Spike API can access their data. Redirect the user to the provider's consent page using a URL obtained via the API. Other providers rely on SDK functionality for integration and data extraction. Refer to SDK Documentation for Apple Health, Health Connect & Samsung Health Data integrations. ## Step 1: Configure Redirect URL in Admin Console To configure the redirect URL in the admin console, navigate to the "Applications" section and locate the "Redirect URLs" option within your application. Here, you can either specify a default redirect URL or add multiple domains to the whitelist which would allow you to define your own redirect URL during the integration process. redirect ## Step 2: Obtain Redirect URL ```mermaid theme={null} sequenceDiagram participant A as Your App participant S as Spike API participant P as Provider (e.g., Fitbit) participant U as End User A->>+S: GET /providers/{provider}/integration/init_url
Authorization: Bearer S-->>-A: 200 OK
Integration URL A->>U: Redirect to integration URL U->>+P: User authorizes data access P-->>-U: Authorization granted P->>S: Provider sends authorization code S->>A: Redirect user to your app
with success confirmation Note over S: Provider integration
is now active for user ``` When you want to initiate the integration process for an end user, you can use the following endpoint to get a redirect URL for a specific provider. ### Query arguments * (Optional) **redirect\_uri** URI to redirect the end user after integration is complete, domain must be **whitelisted** in the admin console. * (Optional) **state** an arbitrary string that would be returned to you after integration is complete. * (Optional) **provider\_user\_id** an id of application users at a specific provider. For e.g., Ultrahuman requires to pass email. ### Example Request ```bash theme={null} curl -X GET https://app-api.spikeapi.com/v3/providers/fitbit/integration/init_url -H 'Authorization: Bearer ' ``` ### Example Response ```json theme={null} { "provider_slug": "fitbit", "path": "https://app-api.spikeapi.com/v3/providers/fitbit/integration/init/ic/la54yZgBJYlokLDHjh_NRpR6HOfFxkGKwEdRWL93gE0DnqBBqGded5SIcdZVAEdLDO-bGYheFBxZly9Q" } ``` ## Redirect the User Redirect the user to the **path** URL provided in the response. This allows them to authorize data access. Once the user is integrated, they would be redirected back to your application either using the default redirect URL or using the **redirect\_uri** provided in your request to the API. # Querying Data Source: https://docs.spikeapi.com/api-docs/query_options_comparison Comparison of Query Options for Developers This document provides a comparison of the different query options available in our API, helping developers choose the right option for their needs. Below, we outline the key differences between querying provider records, statistics, sleep data, workout data, and samples. ### Query Options Overview * **Statistics**: This option is best for aggregated data over a specified time period. It allows you to see trends and patterns by summing or averaging data points. For more information, see the [Statistics Documentation](../api-reference/query-statistics). * **Sleep Data**: If you need to access sleep-related activities, this option provides endpoints to list and retrieve sleep activities. It includes options to include sleep stages and samples. Check the [Sleep Documentation](../api-reference/query-sleep-list) for further details. * **Workout Data**: This option is tailored for querying workout-related activities. It provides detailed insights into workout sessions. For more information, visit the [Workout Documentation](../api-reference/query-workouts-list). * **Time Series**: Use this option for querying raw or minimally processed data points. It is useful for detailed analysis of specific metrics over time. For more details, refer to the [Time Series Documentation](../api-reference/query-timeseries). * **Provider Records**: This option allows you to query detailed records from various providers. However, due to the complexity and difficulty in parsing this data, it is recommended primarily for debugging purposes or when you need data that closely mirrors what the provider offers. For more details, refer to the [Provider Records Documentation](../api-reference/query-provider-records-list). ### Choosing the Right Query Option When deciding which query option to use, consider the following: * **Data Granularity**: If you need detailed, raw data, consider using Samples. Provider Records should be used cautiously due to its complexity and is best suited for debugging or when provider-specific data is necessary. For aggregated insights, Statistics might be more appropriate. * **Specific Use Cases**: For sleep or workout-specific data, use the respective Sleep or Workout query options. * **Integration Needs**: Consider how the data will be integrated into your application and choose the option that best fits your data processing and analysis requirements. By understanding the differences between these query options, developers can make informed decisions on which API endpoints to use for their specific needs. For more detailed information, please refer to the linked documentation for each query option. # Release Notes Source: https://docs.spikeapi.com/api-docs/releases Release notes for the Spike API. * Implemented time-based one-time password (TOTP) for two-factor authentication in the admin console. * Enhanced ingestion of intraday metrics from Samsung devices. * Enabled ingestion of large paginated payloads from Oura. * Enabled Strava integration through the mobile application. * Enhanced provider integration flow. * Added support for new activity types: `work`, `functional_fitness`, `stretching`, `stroller`, `netball`, `paintball`, `polo`, `jumping`, and `parkour`. * Enabled ingestion of new activity types from Whoop. * Removed the `sleep` activity type from Oura daily sleep records. * Added a Billing view for client users in the admin console. * Added ingestion of `duration_low_intensity`, `duration_moderate_intensity`, `duration_high_intensity` from Oura, Withings, Fitbit and Huawei. * Fixed ingestion of Oura-specific `sleep_score` from Oura daily sleep. * Added ingestion of Oura-specific `readiness_score` from Oura sleep. * Improved support for Huawei regions. * Added metrics `duration_low_intensity`, `duration_moderate_intensity`, `duration_high_intensity` to daily statistics. * Added ingestion of intensity durations from Garmin. * Fixed permission errors in admin console. * Updated `GET /queries/workouts` to return `null` for samples with no values. * Added ingestion of `duration_active` from Strava workout. * Fixed ingest of ECG to become available in `GET /queries/timeseries` endpoint. * Improved integration with Freestyle Libre. * Added Padel, Dance to activity types for Strava. * Added swimming distance for Strava ingest. * Updated documentation of ingested metrics from Omron and Samsung Health Data. * Fixed statistics of overlapping epochs from Garmin. * Fixed missing count of webhooks in admin console. * Fixed ingest of Libre data timing. * Exposed meaningful zero values for `sleep_skin_temperature_deviation`. * Fixed `duration_active` to be ingested in milliseconds. * Improved stability of fetching Fitbit data. * Fixed backfill of Oura historical data for the current day. * Improved integration process with Garmin. * Added more clarity to error messages during Nutrition analysis. * Added Flutter and Samsung Health Data SDK integration examples in the admin console. * Fixed fetching of Fitbit ECG data. * Optimized analysis of Nutrition images. * Improved detection of Polar device type. * Added React Native and Expo SDK integration examples in the admin console. * Added ingest of activity-related distance in Coros workout. * Improved analysis of Nutrition images. * Added an option to generate application code examples in admin console. * Fixed the ingest of ECG from Fitbit. * Added backfill of Garmin bodyComps, stressDetails, userMetrics, pulseOx, respiration, bloodPressures and skinTemp. * Improved backfill of Oura data. * Added an option to create application user in admin console for dev/test purposes. * Added an option to generate user credentials in admin console for dev/test purposes. * Improved provider integration process. * Added ingest of steps from Samsung workout, aggregated basal calories. * Switched to a new Claude model for the fast AI analysis of nutrition. * Updated specification of Nutrition AI API. * Fixed the validation of Oura subscriptions. * Fixed ingest of Garmin unmeasurable sleep stages. * Updated specification of Nutrition AI API. * Added support of limited Garmin backfill. * Added ingest of optional `user_time_offset_minutes` field in Nutrition AI API endpoints. * Improved Garmin backfill logic. * Added ingest of `body_temperature` from Withings intraday activity. * Fixed resolution of timezone hours shift during ingest of Fitbit intraday metrics. * Increased the allowed size of payloads from Garmin. * Allowed ingest of larger payloads from Garmin. * Added ingest of exercise from Samsung Health Data. * Added support of `device` input method from Samsung Health Data. * Added ingest of swimming specific metrics from Samsung Health Data. * Added ingest of sleep durations, latencies and interruptions from Samsung Health Data. * Added ingest of user profile from Samsung Health Data. * Added ingest of Whoop specific recovery score. * Increased backend performance and stability. * Added ingest of temperature, glucose, heart rate, spo2, sleep, body comp, blood pressure from Samsung. * Added endpoint `POST /nutrition_records/manual` for uploading manually created nutrition record. * Added endpoint `PUT /nutrition_records/{id}` for replacing a nutrition record with the new one. * Added endpoint `POST /nutrition_records/ingredient/image` for recognition of nutrition facts label. * Added endpoint `POST /nutrition_records/image` instead of a deprecated `POST /nutrition_records`. * Added a blacklist of bundle IDs that are ignored when ingesting sleeps from Apple Health Kit. * Updated ingest of Fitbit intraday heart rates to fetch 1 min samples instead of 15 min. * Fixed deauthorization for provider users. * Allowed workouts with zero duration from Apple Health Kit. * Set `to_timestamp` input to be non-inclusive in time series endpoint. * Revised selection and merge methods of data points in time series endpoint. * Added support for more workout types from Oura. * Added backfill and fetch of Omron data. * Fixed pagination of Fitbit ECG data fetch. * Added timeouts to Nutrition AI and Lab Reports processing. * Improved error handling for Nutrition AI. * Fixed ingest of SkinTemperature from Garmin. * Improved ingest of data from Fitbit. * Fixed user re-authentication during migration to prevent broken integration. * Added deauthorization of `Strava` integration. * Updated OpenAI library from v2 to v3. * Fixed MCP tools for `user_info` and `user_properties`. * Added ingestion of Huawei `sleep_latency` from sleep data. * Added gender type `Other` to `user_properties`. * Improved Whoop integration flow. * Skipped ingestion of Suunto sleep ID until Suunto fixes the API. * Fixed Fitbit SpO2 summary ingest. * Updated versions of AI models to be used for Lab Reports. * Added support for new Oura authentication flow. * Added ingestion of Omron blood pressure data and hourly activity summary. * Added support for Omron US and EU regions. * Introduced MCP support for `Lab Reports`. * Added ingestion of Garmin `sleep_skin_temperature_deviation`. * Added `DELETE /nutrition_records/{id}` endpoint. * Added `PATCH /nutrition_records/{id}` endpoint. * Removed `sleep_duration_total` from `/queries/statistics/interval`. * Added ingestion of Huawei metrics `skin_temperature` and `spo2`. * Fixed processing of nutrition files of type `webp`. * Optimized fetch of Fitbit calories overnight. * Deprecated `heartrate_resting_min` and `heartrate_resting_max`. * Improved data fetching. * Added ingestion of Fitbit and Polar `sleep_skin_temperature_deviation` data. * Garmin epoch data fixed in `/queries/statistics/interval`. * Auto-detect mime-type on `/lab_reports` upload. * Improved admin console dashboard. * Added Oura Resting Heart Rate metric in Sleep record. * Added more metrics to Ultrahuman sleep. * Added Mindfulness Session support to MCP. * Added `body_url` to `/nutrition_records`. * Resolved a potential deadlock issue to improve system stability. * Added ingestion of skin temperature from Ultrahuman sleep data. * Improved admin console UI. * Added ingestion of Mindfulness session from Apple HealthKit and Health Connect. * Oura subscription support. * Ultrahuman integration improvements. * Require Fitbit heartrate permissions for intraday data ingestion. * Nutrition AI improvements and enhancements. * Fitbit backfill optimization. * Huawei support in China region. * Whoop API v2 support with enhanced data collection and improved webhooks. * Nutrition AI improvements with liquid identification capabilities and analysis mode. * Garmin backfill optimization * Lab report webhooks now include `application_user_id` and send full LabReport data. * Added `hrv_rmssd` and `hrv_sdnn` to `/queries/statistics/daily`. * Enhanced application manifest handling with defaults and type improvements. * Improved nutrition records with modified timestamp support and webhook configurations. * Withings intraday data ingestion fixes. * MCP support for nutrition records. * Enhanced `/nutrition_records` endpoints with AI processing. * `/lab_reports` endpoints now support webhooks and YAML responses. * Added `stress_score` metric. * Improved error handling for `/lab_reports` and `/nutrition_records`. * Better device type classification for Health Connect. * New `/lab_reports` endpoints for medical test analysis. * Added `sleep_skin_temperature_deviation` metric from Oura. * `/applicationinfo` endpoint supports application manifest. * Added `activity_score` metric. * Application-level authentication support. * Enhanced heartrate zones support. * Improved route points and samples in workout data. * Enhanced logging and debugging capabilities. * Added `recovery_score` metric. * Route points support in workout samples. * Enhanced samples filtering and readiness score support. * Improved `/queries/statistics/daily` and `/queries/statistics/interval` endpoints. * Deprecated `/queries/statistics` endpoint. * Activity ID generation updates. * Added `body_temperature` metric from Oura. * Enhanced Fitbit provider delete hooks and unsubscription handling. * Prometheus monitoring integration for database operations. * Statistics interpolation endpoint improvements. * Added `stress_score` metric with rollup calculations. * Blood glucose support for Samsung Health, Apple HealthKit, and Health Connect. * Glucose precision increased to 6 decimal places. * Enhanced device type mapping and source info ingestion. * Error callbacks for OAuth integration failures. * Samsung and Coros provider support. * Route points support in workout data. * Apple HealthKit sleep analysis handles overlapping entries. * Coros provider integration. * FIT file processing supports route points and samples. * Blood pressure min/max as sample metrics. * Respiratory metrics `breathing_rate`, `breathing_rate_min`, and `breathing_rate_max` will be used in sleep endpoints instead of the deprecated `sleep_breathing_rate`, `sleep_breathing_rate_min`, and `sleep_breathing_rate_max`. * Steps will be fetched from Polar daily activities. * Fixes and performance improvements. * Implementation of Samsung Health Data SDK support with Android SDK (compatible with Android SDK 4.3.12+). * Proactive backfilling from providers that do not support webhooks for certain metrics. * Fixes and performance improvements. * Added `non_wear` sleep phase to Ultrahuman. * Improved data flow synchronisation for providers not supporting webhooks. * Admin console UI improvements. * Implementation of Dexcom (single region) for glucose metric continuous monitoring. * Implementation of Freestyle Libre provider for glucose metric continuous monitoring. * List metrics from samples and other deeper structures in webhooks. * Samsung Health Data protobuf support with steps, distance, and calories ingestion. * Proactive backfilling system for automatic data synchronization. * Health Connect protobuf support for Android devices. * Enhanced samples endpoint with highest granularity filtering. * Skin temperature support for Android devices. * Fitbit-specific caching improvements. * Ultrahuman glucose metric support for continuous monitoring. * Samsung Health Data provider scaffolding and protobuf integration. * Enhanced Huawei supported metrics. * Performance improvements for digest checks and parallel processing. * Stats event flushing optimization. * Coros provider sync and date casting improvements. * Apple statistics push fixes for quantity type handling. * Enhanced error handling for cancelled contexts. * Provider-specific sleep scores. * Enhanced protobuf samples processing with duration handling. * Apple workout protobuf support improvements. * Joule to kcal calorie type corrections. * Added support for integrating UltraHuman wearables. * Application migration from v2 setup to v3 process implementation (internal). * VO2max metric support across providers. * Enhanced audit event logging for provider integrations. * Strava provider integration with activity fetching. * User properties improvements with BMI calculations in meters and grams. * Apple walking speed metric ingestion. * Added support for multiple event types in audit events. * Custom provider data included in `/queries/provider_records`. * Body metrics support (BMI, body fat, blood pressure) across multiple providers. * Garmin blood pressure and body composition ingestion. * Apple ECG support with protobuf integration. * Enhanced user properties system with automatic BMI calculation. * Route points support in workout data. * Audit events system implementation. * New time range parameters, BMI, vo2max metrics. * Added endpoint for daily statistics. * Implemented new communication of SDK (iOS based) with backend using Protobuf. Reduces latency and overall network throughput. * Support unknown sports in fit files that are defined by third party apps. * Enhanced vo2max metric support. * Huawei sleep duration fixes for missing awake stages. * Fitbit sleep ingestion improvements. * Trace-level logging optimizations. * Comprehensive time range filtering and query system. * Enhanced samples endpoint with grouping and filtering capabilities. * Apple protobuf sleep and workout ingestion. * Health Connect sleep phase support. * Provider records timezone and filtering improvements. * Enhanced webhook metadata and logging. * Enhanced workout activity type filtering and tagging. * Improved Health Connect activity type mapping. * Whoop statistics query improvements. * Fitbit cardio fitness scope support. * Polar ECG processing enhancements. * Unauthorized datatype handling improvements. * Activity type filtering fixes. * Enhanced sleep query testing and validation. * Improved metric prioritization and summary handling. * Timezone handling improvements for queries. * Performance optimizations for query processing. * Enhanced error handling for health data ingestion. * Improved test coverage for provider integrations. * Sleep metrics ingestion improvements across providers. * Enhanced data validation and testing frameworks. * Provider-specific metric handling optimizations. * Oura, Garmin, and Polar sleep ingestion enhancements. * Improved type matching and validation. * Enhanced retention policy management. * Sleep phase processing across multiple providers. * Enhanced activity and workout data handling. * Improved metric conversion and validation. * Enhanced sleep ingestion with stage support. * Improved provider data synchronization. * Activity entry consolidation improvements. * Garmin sleep processing with prioritization. * Enhanced sleep metrics across providers. * Improved data validation and error handling. * Comprehensive sleep stage ingestion system. * Enhanced respiration metrics support. * Improved multi-provider sleep data handling. * Sleep duration and efficiency metrics. * Enhanced Fitbit and Whoop sleep processing. * Improved data quality validation. * Advanced sleep metrics processing. * Enhanced provider-specific sleep handling. * Improved data synchronization and validation. * Sleep data processing enhancements. * Improved metric conversion and validation. * Enhanced provider integration stability. * Core sleep ingestion improvements. * Enhanced data validation and error handling. * Provider synchronization optimizations. * Sleep stage processing foundation. * Enhanced metric validation and conversion. * Improved provider data handling. * Major sleep ingestion system implementation. * Support for sleep stages across all providers. * Enhanced activity and workout processing. * Comprehensive metric validation system. * Enhanced activity entry processing. * Improved sleep metric consolidation. * Apple sleep grouping implementation. * Sleep processing optimizations. * Enhanced data validation frameworks. * Improved provider integration stability. * Core sleep infrastructure improvements. * Enhanced metric processing and validation. * Provider data synchronization enhancements. * Sleep data ingestion foundation. * Enhanced provider metric handling. * Improved data quality validation. * Initial sleep processing improvements. * Enhanced provider data handling. * Core metric validation enhancements. * Comprehensive sleep ingestion system foundation. * Enhanced Garmin, Fitbit, Polar, and Whoop sleep support. * Apple sleep stages implementation. * Health Connect activity processing improvements. * Enhanced data validation and testing frameworks. * Core platform stability improvements. * Enhanced provider integration framework. * Initial sleep processing capabilities. * Provider integration optimizations. * Enhanced data processing capabilities. * Core platform improvements. * Enhanced provider data handling. * Improved system stability and performance. * Core infrastructure optimizations. * Provider integration improvements. * Enhanced data validation and processing. * Core system optimizations. * Initial v3.0 platform release. * Core provider integration framework. * Enhanced data processing architecture. * Foundation for comprehensive health data ingestion. # Webhooks Source: https://docs.spikeapi.com/api-docs/webhooks To receive data updates, configure a webhook endpoint via the admin console. After each data update from the provider, a webhook event will be sent to your endpoint. ```mermaid theme={null} sequenceDiagram participant P as Provider participant S as Spike API participant W as Your Webhook P->>S: New health data received S->>S: Process and store data S->>+W: POST webhook event
X-Body-Signature header alt Success (HTTP 200) W-->>S: 200 OK Note over S: Event delivered successfully else Failure (non-200, timeout, or network error) W-->>S: Error response Note over S: Retry after 5 seconds S->>+W: POST webhook event (retry) W-->>S: Error response Note over S: Retry after 2 minutes S->>+W: POST webhook event (retry) W-->>S: Error response Note over S: Continue retrying up to 10 attempts
Final retries every 12 hours S->>+W: POST webhook event (final retry) W-->>S: Error response Note over S: Event discarded after all retries end ``` Your endpoint **must respond with HTTP 200** to acknowledge a successful receipt. If the request fails due to a network error, exceeds 30 seconds to complete or returns any status code other than 200, the system will retry the request **up to 10 times** with exponential backoff. Retries are timed at first after 5 sec, then 2 min, 30 min, 2 hours and then the rest every 12 hours. After the final attempt, the event will be discarded. ## Webhook event Payload * **application\_user\_id** The application user ID you've provided when getting the access token * **timestamp** Time of the event * **event\_type** * **record\_change** new or updated data received from the provider * **provider\_integration\_created** user has integrated with a provider * **provider\_integration\_deleted** user integration has been deleted * **metrics** metric types involved with the event * **activity\_types** activity types if any involved with the event * **provider\_slug** provider triggering the event * **earliest\_record\_start\_at** earliest timestamp of records involved with the event in ISO 8601 format * **latest\_record\_end\_at** latest timestamp of records involved with the event in ISO 8601 format ### Example Payload * **record\_change** ```json theme={null} [ { "application_user_id": "User1", "timestamp": "2025-04-15T13:33:55.271331177Z", "event_type": "record_change", "metrics": ["calories_burned_active", "distance", "steps"], "activity_types": ["sedentary", "walking"], "provider_slug": "garmin", "earliest_record_start_at": "2025-04-15T09:30:00Z", "latest_record_end_at": "2025-04-15T13:33:00Z" }, { "application_user_id": "User2", "timestamp": "2025-04-15T13:33:55.271331177Z", "event_type": "record_change", "metrics": ["calories_burned_active"], "activity_types": ["walking"], "provider_slug": "garmin", "earliest_record_start_at": "2025-04-15T09:30:00Z", "latest_record_end_at": "2025-04-15T13:33:00Z" } ] ``` * **provider\_integration\_created** ```json theme={null} [ { "application_user_id": "test", "timestamp": "2025-08-29T13:21:10.703300387Z", "event_type": "provider_integration_created", "provider_slug": "oura" } ] ``` ## Signature Each webhook event is signed using an HMAC-SHA256 signature for verification. The signature is included in the **X-Body-Signature** header. The signature is computed by signing the raw request body **as-is** using a shared secret key. You can retrieve this key from the admin console. To verify authenticity: 1. Compute the HMAC-SHA256 hash of the request body using the shared key. 2. Compare the result to the value in the X-Body-Signature header. Console Client Webhook Pn ## Code Examples ```go [Go] theme={null} package main import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "io" "net/http" "time" ) // PushEvent represents a webhook event from the health data provider const hmacKey = "HMAC_KEY_FROM_ADMIN_CONSOLE" type PushEvent struct { ApplicationUserID string `json:"application_user_id"` // ID of the application user Timestamp time.Time `json:"timestamp"` // Event timestamp EventType string `json:"event_type"` // Type of event Metrics []string `json:"metrics"` // List of metrics ActivityTypes []string `json:"activity_types"` // List of activity types ProviderSlug string `json:"provider_slug"` // Provider identifier EarliestRecordStartAt time.Time `json:"earliest_record_start_at"` // Start of data range LatestRecordEndAt time.Time `json:"latest_record_end_at"` // End of data range } func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { // Verify HMAC signature signature := r.Header.Get("X-Body-Signature") if signature == "" { http.Error(w, "Missing signature", http.StatusBadRequest) return } // Read and verify the request body body, err := io.ReadAll(r.Body) if err != nil { http.Error(w, "Failed to read body", http.StatusInternalServerError) return } // Calculate HMAC hm := hmac.New(sha256.New, []byte(hmacKey)) hm.Write(body) if signature != hex.EncodeToString(hm.Sum(nil)) { http.Error(w, "Invalid signature", http.StatusUnauthorized) return } // Parse events var events []PushEvent if err := json.Unmarshal(body, &events); err != nil { http.Error(w, "Invalid JSON", http.StatusBadRequest) return } // Process events for _, event := range events { fmt.Printf("Received event: %+v\n", event) } w.WriteHeader(http.StatusOK) w.Write([]byte("OK")) }) fmt.Println("Starting server on port 8000") http.ListenAndServe(":8000", nil) } ``` ```javascript [JavaScript] theme={null} const crypto = require("crypto"); const HMAC_KEY = "HMAC_KEY_FROM_ADMIN_CONSOLE"; class PushEvent { constructor(data) { this.application_user_id = data.application_user_id; this.timestamp = new Date(data.timestamp); this.event_type = data.event_type; this.metrics = data.metrics; this.activity_types = data.activity_types; this.provider_slug = data.provider_slug; this.earliest_record_start_at = new Date(data.earliest_record_start_at); this.latest_record_end_at = new Date(data.latest_record_end_at); } } const server = require("http").createServer((req, res) => { if (req.method !== "POST") { res.statusCode = 405; res.end("Method not allowed"); return; } let body = ""; req.on("data", (chunk) => (body += chunk)); req.on("end", () => { // Verify signature const signature = req.headers["x-body-signature"]; if (!signature) { res.statusCode = 400; res.end("Missing signature"); return; } // Calculate HMAC const hmac = crypto.createHmac("sha256", HMAC_KEY); hmac.update(body); if (signature !== hmac.digest("hex")) { res.statusCode = 401; res.end("Invalid signature"); return; } // Parse events let events = JSON.parse(body).map((data) => new PushEvent(data)); // Process events for (const event of events) { console.log("Received event:", event); } res.statusCode = 200; res.end("OK"); }); }); server.listen(8000); ``` ```php [PHP] theme={null} application_user_id = $eventData['application_user_id']; $event->timestamp = new DateTime($eventData['timestamp']); $event->event_type = $eventData['event_type']; $event->metrics = $eventData['metrics']; $event->activity_types = $eventData['activity_types']; $event->provider_slug = $eventData['provider_slug']; $event->earliest_record_start_at = new DateTime($eventData['earliest_record_start_at']); $event->latest_record_end_at = new DateTime($eventData['latest_record_end_at']); echo "Received event: " . print_r($event, true) . "\n"; } http_response_code(200); echo 'OK'; } // Handle the request handleRequest(); ``` ```python [Python] theme={null} import hmac import hashlib import json from datetime import datetime from http.server import HTTPServer, BaseHTTPRequestHandler from typing import List, Optional class PushEvent: """Represents a health data push event from a provider.""" def __init__(self, data: dict): self.application_user_id: str = data['application_user_id'] self.timestamp: datetime = datetime.fromisoformat(data['timestamp']) self.event_type: str = data['event_type'] self.metrics: List[str] = data['metrics'] self.activity_types: List[str] = data['activity_types'] self.provider_slug: str = data['provider_slug'] self.earliest_record_start_at: datetime = datetime.fromisoformat(data['earliest_record_start_at']) self.latest_record_end_at: datetime = datetime.fromisoformat(data['latest_record_end_at']) class RequestHandler(BaseHTTPRequestHandler): """Handles incoming webhook requests with HMAC verification.""" HMAC_KEY = "HMAC_KEY_FROM_ADMIN_CONSOLE" def _verify_signature(self, body: bytes, signature: Optional[str]) -> bool: """Verifies the HMAC signature of the request body.""" if not signature: return False h = hmac.new(self.HMAC_KEY.encode(), body, hashlib.sha256) return signature == h.hexdigest() def do_POST(self): """Handles POST requests with webhook events.""" # Read request body content_length = int(self.headers.get('Content-Length', 0)) body = self.rfile.read(content_length) # Verify signature if not self._verify_signature(body, self.headers.get("X-Body-Signature")): self.send_error(401, "Invalid signature") return # Parse and process events try: events = [PushEvent(data) for data in json.loads(body)] for event in events: print(f"Received event: {event.__dict__}") except (json.JSONDecodeError, KeyError, ValueError): self.send_error(400, "Invalid JSON") return # Send success response self.send_response(200) self.end_headers() self.wfile.write(b"OK") def main(): """Starts the HTTP server.""" server = HTTPServer(('', 8000), RequestHandler) server.serve_forever() if __name__ == "__main__": main() ``` ```typescript [TypeScript] theme={null} import { createHmac } from "crypto"; import { createServer, IncomingMessage, ServerResponse } from "http"; // Configuration const HMAC_KEY = "HMAC_KEY_FROM_ADMIN_CONSOLE"; const PORT = 8000; // Type definitions interface PushEvent { application_user_id: string; timestamp: Date; event_type: string; metrics: string[]; activity_types: string[]; provider_slug: string; earliest_record_start_at: Date; latest_record_end_at: Date; } // Helper functions const verifySignature = ( body: string, signature: string | undefined ): boolean => { if (!signature) return false; const hmac = createHmac("sha256", HMAC_KEY); hmac.update(body); return signature === hmac.digest("hex"); }; const parseEvents = (body: string): PushEvent[] => { return JSON.parse(body).map((data: any) => ({ ...data, timestamp: new Date(data.timestamp), earliest_record_start_at: new Date(data.earliest_record_start_at), latest_record_end_at: new Date(data.latest_record_end_at), })); }; // Server setup const server = createServer((req: IncomingMessage, res: ServerResponse) => { // Only accept POST requests if (req.method !== "POST") { res.statusCode = 405; res.end("Method not allowed"); return; } // Collect request body let body = ""; req.on("data", (chunk) => (body += chunk)); req.on("end", () => { try { // Verify request signature if ( !verifySignature( body, req.headers["x-body-signature"] as string | undefined ) ) { res.statusCode = 401; res.end("Invalid signature"); return; } // Parse and process events const events = parseEvents(body); events.forEach((event) => console.log("Received event:", event)); // Send success response res.statusCode = 200; res.end("OK"); } catch (error) { // Handle parsing errors res.statusCode = 400; res.end("Invalid request"); } }); }); // Start server server.listen(PORT, () => { console.log(`Server running on port ${PORT}`); }); ``` # Analyze Nutrition Image Source: https://docs.spikeapi.com/api-reference/analyze-nutrition-image post /nutrition_records/image ##### Upload a food image and analyze the nutritional content. The system uses advanced computer vision and machine learning models to identify ingredients, calculate nutritional values, and provide detailed food composition data. ### Processing Flow 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** (recommended) — returns immediately and processes the image in the background (see [Asynchronous Processing](/nutrition-ai/async)); use webhooks for real-time notifications - **Synchronous** — waits for analysis to be completed before responding If your application is configured to use asynchronous processing and ready to consume webhooks, our API will send a webhook notification in both processing modes, synchronous or asynchronous, once the nutritional analysis is completed. ### Analysis Modes - **`precise`** (default) — uses advanced AI models for the highest accuracy and detail with good processing time - **`fast`** — uses optimized AI models for good accuracy and detail with the fastest processing time ### Localization Optionally provide `country_code` and/or `language_code` in lowercase (ISO 3166-1 alpha-2 code) for region-specific analysis. ### Including Optional Data 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` Example of how to include optional fields and custom nutritional fields: ```json { "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" ] } ``` For more documentation, including implementation examples, processing workflows, and integration guides, see **[Implementation Guide](/nutrition-ai/implementation)**. # Analyze Nutrition Facts Label Source: https://docs.spikeapi.com/api-reference/analyze-nutrition-label post /nutrition_records/ingredients/label ##### Upload an image of a nutritional facts label to analyze it. The system uses OCR (Optical Character Recognition) powered by AI to extract nutrition details from nutritional facts label image. The results with nutritional fields like total fat, protein, etc. are structurized and presented as one ingredient in the JSON response. ### Processing Flow The processing of nutrition facts label analysis is synchronous. No webhooks will be sent. ### Analysis Modes - **`precise`** (default) — uses advanced AI models for the highest accuracy and detail with good processing time - **`fast`** — uses optimized AI models for good accuracy and detail with the fastest processing time ### Nutritional fields A predefined list of nutritional fields is extracted. See **[Nutritional Fields](/technical-references/nutritional_fields)** for supported macronutrients and micronutrients. ### Localization For more documentation, including implementation examples, processing workflows, and integration guides, see **[Implementation Guide](/nutrition-ai/implementation)**. # Delete Integration Source: https://docs.spikeapi.com/api-reference/delete-integration delete /providers/{provider_slug}/integration Delete an integration with a provider. # Delete Nutrition Record Source: https://docs.spikeapi.com/api-reference/delete-nutrition-record delete /nutrition_records/{nutrition_record_id} ##### Delete a nutrition report record by ID. An HTTP 204 status code will be returned if successful, regardless if the record existed or not. # Get Lab Report Source: https://docs.spikeapi.com/api-reference/get-lab-report get /lab_reports/{lab_report_id} Retrieve a lab report by record ID. # Get Nutrition Record Source: https://docs.spikeapi.com/api-reference/get-nutrition-record get /nutrition_records/{nutrition_record_id} Retrieve a nutrition record by ID. # List Lab Reports Source: https://docs.spikeapi.com/api-reference/list-lab-reports get /lab_reports Retrieve a list of lab reports by the time range. # List Nutrition Records Source: https://docs.spikeapi.com/api-reference/list-nutrition-records get /nutrition_records Retrieve a list of nutrition records by the time range. # Modify Nutrition Record Source: https://docs.spikeapi.com/api-reference/modify-nutrition-record patch /nutrition_records/{nutrition_record_id} ##### Change the portion size for a nutrition record by ID. You can modify the nutrition record by updating the total serving size or consumption time. At least one of these fields must be provided. The status of the successfully modified record will be set to `updated` while the input type will preserve the original value. ### Updating Serving Size 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. The new serving size is expected to be in the same unit as the original serving size. ### Setting Up Consumption Time The consumption time indicates when the food was actually consumed. Leaving the consumption time empty or not providing it will keep the original value. This allows you to first track planned meals (without consumption time) and eventually mark them as consumed by setting up consumption time. # Get Provider Record Source: https://docs.spikeapi.com/api-reference/query-provider-record-by-id get /queries/provider_records/{record_id} This endpoint allows you to query a single provider record with metrics by ID. It supports including sample data based on time series and provider-specific metrics in the response. The response includes a single record with raw data from the provider transformed into the unified data model, possibly including: - base information (start, end, duration, user time offset, input method, source provider, etc.) - metrics (base and provider-specific) - activity information such as activity ID, type, and tags - activity breakdown into laps, sessions, etc. with related sample data - sleep with sample data All timestamps in the response are in the UTC timezone. # List Provider Records Source: https://docs.spikeapi.com/api-reference/query-provider-records-list get /queries/provider_records This endpoint allows you to query provider records with metrics, optionally filtering by providers. It supports filtering by UTC timestamp range and providers, and includes provider-specific metrics in the response. The response includes records with raw data from providers transformed into the unified data model, each possibly including: - base information (start, end, duration, user time offset, input method, source provider, etc.) - metrics (base and provider-specific) - activity information such as activity ID, type, and tags - activity breakdown into laps, sessions, etc. - sleeps with sample data All timestamps in both request and response are in the UTC timezone. # Get Sleep Source: https://docs.spikeapi.com/api-reference/query-sleep-by-id get /queries/sleeps/{sleep_id} This endpoint returns sleep data including different sleep stages that represent the various phases of sleep a person goes through during a sleep cycle. Each stage is characterized by distinct physiological and neurological patterns. The different sleep stages are: - **awake**: The state of being fully conscious and alert. - **sleeping**: A general term for being in a state of sleep. - **out_of_bed**: The state when a person is not in bed, possibly indicating a brief interruption in sleep. - **light**: A stage of sleep where the body begins to relax, and the heart rate slows down. It is easier to wake up from this stage. - **deep**: A restorative stage of sleep where the body repairs and regrows tissues, builds bone and muscle, and strengthens the immune system. - **rem**: Rapid Eye Movement sleep, a stage where dreaming occurs, and the brain is very active. It plays a role in memory consolidation and mood regulation. - **awake_in_bed**: The state of being awake while still in bed, often occurring before falling asleep or after waking up. - **nap**: A short sleep, typically taken during the day, that can help improve alertness and performance. - **unknown**: A stage that cannot be classified into any of the known categories, possibly due to insufficient data or anomalies in the sleep pattern. ### Sleep Attribution Date The sleep date attribution follows two simple rules to determine which date a sleep record should be associated with: 1. **Provider Payload Date**: The primary source of truth is the date provided in the payload from the sleep tracking provider. This date is used if available. 2. **End Date Rule**: If the provider payload date is not available, the sleep date is attributed to the day on which the sleep session ended. This straightforward approach ensures consistent date attribution for sleep records, regardless of when the sleep session started or ended. ### Main Sleep and Nap There are two types of sleep: main sleep and nap. There can be only one main sleep per day, which is the longest sleep period. A nap is a shorter sleep taken during the day and will have a `sleep_duration_nap` value. ### Sleep Durations Total sleep duration is the sum of the durations of light, deep, and REM sleep stages, while total time in bed also includes time spent awake. ``` total sleep = light + deep + rem time in bed = total sleep + awake ``` Note that nap time is not included in the total sleep time for a day. Sleep latency refers to the estimated time from when rest begins until the first occurrence of light or deep sleep. The accuracy of sleep stages and latency depends entirely on the provider’s sensors and calculations. Sleep duration values are reported in milliseconds. # List Sleeps Source: https://docs.spikeapi.com/api-reference/query-sleep-list get /queries/sleeps This endpoint returns sleep data including different sleep stages that represent the various phases of sleep a person goes through during a sleep cycle. Each stage is characterized by distinct physiological and neurological patterns. The different sleep stages are: - **awake**: The state of being fully conscious and alert. - **sleeping**: A general term for being in a state of sleep. - **out_of_bed**: The state when a person is not in bed, possibly indicating a brief interruption in sleep. - **light**: A stage of sleep where the body begins to relax, and the heart rate slows down. It is easier to wake up from this stage. - **deep**: A restorative stage of sleep where the body repairs and regrows tissues, builds bone and muscle, and strengthens the immune system. - **rem**: Rapid Eye Movement sleep, a stage where dreaming occurs, and the brain is very active. It plays a role in memory consolidation and mood regulation. - **awake_in_bed**: The state of being awake while still in bed, often occurring before falling asleep or after waking up. - **nap**: A short sleep, typically taken during the day, that can help improve alertness and performance. - **unknown**: A stage that cannot be classified into any of the known categories, possibly due to insufficient data or anomalies in the sleep pattern. ### Sleep Attribution Date The sleep date attribution follows two simple rules to determine which date a sleep record should be associated with: 1. **Provider Payload Date**: The primary source of truth is the date provided in the payload from the sleep tracking provider. This date is used if available. 2. **End Date Rule**: If the provider payload date is not available, the sleep date is attributed to the day on which the sleep session ended. This straightforward approach ensures consistent date attribution for sleep records, regardless of when the sleep session started or ended. ### Main Sleep and Nap There are two types of sleep: main sleep and nap. There can be only one main sleep per day, which is the longest sleep period. A nap is a shorter sleep taken during the day and will have a `sleep_duration_nap` value. ### Sleep Durations Total sleep duration is the sum of the durations of light, deep, and REM sleep stages, while total time in bed also includes time spent awake. ``` total sleep = light + deep + rem time in bed = total sleep + awake ``` Note that nap time is not included in the total sleep time for a day. Sleep latency refers to the estimated time from when rest begins until the first occurrence of light or deep sleep. The accuracy of sleep stages and latency depends entirely on the provider’s sensors and calculations. Sleep duration values are reported in milliseconds. # Daily Statistics Source: https://docs.spikeapi.com/api-reference/query-statistics-daily get /queries/statistics/daily This endpoint returns daily statistics calculated based on the data available for a specific time period. The data is aggregated for each day based on the user's local timezone. Refer to the different statistics types in the [Statistics Types](/technical-references/statistics_matrix) section for more information on how the data is aggregated for each type. #### User's Local Timezone The `/statistics/daily` endpoint uses the user's local timezone for all its calculations. This approach ensures that the data reflects the user's actual daily activity patterns, providing a more personalized view. By using the local timezone, the API tailors the data to the user's specific context, which is particularly useful for applications that focus on individual user experiences. This approach contrasts with the `/statistics/interval` endpoint, which uses Coordinated Universal Time (UTC) for calculations, offering a standardized global view. # Interval Statistics Source: https://docs.spikeapi.com/api-reference/query-statistics-interval get /queries/statistics/interval This endpoint returns statistics calculated based on the data available for a specific time period. The data is aggregated according to the time interval specified in your request, allowing you to see trends and patterns over time. Refer to the different statistics types in the [Statistics Types](/technical-references/statistics_matrix) section for more information on how the data is aggregated for each type. #### Coordinated Universal Time The `/statistics/interval` endpoint uses Coordinated Universal Time (UTC) for all its calculations. This standardization ensures that data is consistent and comparable across different time zones. By using UTC, the API provides a global view of the data, which is crucial for applications that require a uniform time reference. This approach contrasts with the `/statistics/daily` endpoint, which uses the user's local timezone for calculations, offering a more personalized view. #### Selection of Provider Source Within a single statistics interval there can be data aggregated from one provider source only. # Time Series Source: https://docs.spikeapi.com/api-reference/query-timeseries get /queries/timeseries This endpoint returns a time series for a given metric with automatic merge strategy selection. The system chooses the optimal merge method based on metric characteristics to ensure the best data quality without manual configuration. ## Request Example ```json { "from_timestamp": "2024-01-01T00:00:00Z", "to_timestamp": "2024-01-02T00:00:00Z", "metric": "heartrate" } ``` ## Response Example ```json { "metric": "heartrate", "start_at": "2024-01-01T00:00:00Z", "offsets": [0, 60000, 120000], "durations": [60000, 60000, 60000], "values": [100, 150, 200], "providers": ["garmin"] } ``` The arrays `offsets`, `durations`, and `values` will all be of the same length. This alignment is crucial for interpreting the data correctly. ## Overriding the Default Merge Method To override the default merge strategy, use the `merge_method` parameter. The default merge method is automatically selected based on the metric type and provides optimal data quality for most use cases. Only override the default if you have specific requirements that cannot be met by the automatic selection. This parameter is intended for advanced users who understand the implications of different merge strategies. ### Source Selection Methods Selects data from a single best provider source based on criteria like coverage, granularity, or priority. Only one source contributes to the final result, ensuring consistency but potentially losing data from other sources. - **`select_best_weighted_source`**: Best weighted coverage score mix of time coverage and data point density - **`select_highest_coverage_source`**: Highest total time coverage (sum of all entry durations) - **`select_most_granular_source`**: Most data points (highest entry count) - **`select_highest_priority_source_type`**: Sample > Intraday > Summary > Activity priority, then weighted coverage ### Coverage Optimization Methods Maximizes time coverage by selecting non-overlapping entries from multiple sources. Prioritizes filling time gaps while avoiding overlaps, using interval scheduling algorithms. - **`merge_maximize_coverage`**: Weighted interval scheduling to maximize total time coverage - **`merge_maximize_granularity`**: Source with most data points, then greedy interval scheduling - **`merge_maximize_weighted_coverage`**: Source with best weighted coverage score, then greedy interval scheduling ### Sample Merging Methods Combines all sample data points, resolving conflicts at identical timestamps by selecting the source with the most total entries. Preserves all available data while handling timestamp collisions. - **`merge_all_samples`**: All zero-duration samples, conflicts resolved by source with most total entries # Get Workout Source: https://docs.spikeapi.com/api-reference/query-workout-by-id get /queries/workouts/{workout_id} This endpoint returns detailed information about a single workout by its activity ID. It provides comprehensive workout data including all available metrics, breakdowns, and associated data points for the specified activity. ### Workout Details A single workout contains complete information about the activity: - **Base Information**: Start time, end time, duration, user time offset, input method, and source provider - **Activity Metadata**: Workout ID, name, type, and associated tags - **Metrics**: Base metrics (distance, calories, duration) and provider-specific metrics - **Detailed Breakdowns**: Sessions, laps, splits, segments, and route information - **Sample Data**: Time-series data points with offsets, durations, and values ### Data Components The endpoint supports various inclusion parameters to customize the response: - **Sessions**: Include workout session breakdowns with detailed metrics - **Laps**: Include lap-by-lap data for structured activities with lap numbers - **Samples**: Include detailed time-series data points with metric values - **Route Points**: Include GPS coordinates and elevation data for route visualization - **Splits**: Include split times and metrics for timed activities - **Segments**: Include segment breakdowns for complex workouts ### Sample Data Structure When samples are included, the response contains time-series data with: - **Offsets**: Time offsets from the workout start in milliseconds - **Durations**: Duration of each data point in milliseconds - **Values**: Metric values organized by metric type (heart rate, pace, etc.) ### Route Information Route points provide GPS and location data: - **Latitude/Longitude**: Geographic coordinates for route visualization - **Elevation**: Altitude data when available - **Timing**: Precise timing information for each route point ### Activity Breakdowns Complex workouts may include multiple breakdowns: - **Sessions**: Different phases or segments of the workout - **Laps**: Individual laps for track or structured activities - **Splits**: Time splits for performance analysis - **Segments**: Custom segments defined by the activity All timestamps in the response are in the UTC timezone. # List Workouts Source: https://docs.spikeapi.com/api-reference/query-workouts-list get /queries/workouts This endpoint returns a list of workouts for a user, including various types of physical activities and exercises. Workouts represent structured physical activities that can be tracked and analyzed for fitness and health purposes. ### Workout Types Workouts encompass a wide variety of physical activities. For a complete list of available workout types, see the [Activity Types](/api-docs/activity_matrix) documentation. ### Workout Structure Each workout contains: - **Base Information**: Start time, end time, duration, and provider details - **Activity Details**: Workout type, name, tags, and unique identifier - **Metrics**: Distance, calories, heart rate, pace, and other relevant measurements - **Optional Components**: Sessions, laps, samples, route points, splits, and segments ### Data Inclusion Options The endpoint supports various inclusion parameters to customize the response: - **Sessions**: Include workout session breakdowns - **Laps**: Include lap-by-lap data for structured activities - **Samples**: Include detailed time-series data points - **Route Points**: Include GPS coordinates and route information - **Splits**: Include split times for timed activities - **Segments**: Include segment breakdowns for complex workouts ### Activity Tags Workouts are categorized using activity tags that help classify the type of activity. For a complete list of available activity tags, see the [Activity Types](/api-docs/activity_matrix) documentation. All timestamps in both request and response are in the UTC timezone. # Replace Nutrition Record Source: https://docs.spikeapi.com/api-reference/replace-nutrition-record put /nutrition_records/{nutrition_record_id} ##### Replace an existing nutrition record. You can replace an existing nutrition record. All information of the old one will be deleted, and only the new information will be retained. The `record_id` will remain the same. The status of the successfully replaced record will be set to `updated` while the input type will be updated to `manual` regardless of the original value. The language of the provided dish name and description will be neither identified nor validated. # Upload Lab Report Source: https://docs.spikeapi.com/api-reference/upload-lab-report post /lab_reports Upload and process a base64 encoded document. # Upload Nutrition Record Source: https://docs.spikeapi.com/api-reference/upload-nutrition-record post /nutrition_records/manual ##### Upload a manually created nutrition record The values are validated against the schema but not verified to be consistent (e.g., total calories are not verified to be equal to the sum of calories found in the ingredients), but the check for mandatory fields is still enforced. The status of the successfully uploaded record will be set to `completed` and the input type will be set to `manual`. The language of the provided dish name and description will be neither identified nor validated. When available, provide `dish_name` in English and use the `dish_name_translated` field for the dish name in the local language. Otherwise, populate both the `dish_name_translated` and mandatory `dish_name` fields with the localized dish name provided by the user. # User Information Source: https://docs.spikeapi.com/api-reference/user-info get /userinfo Get information about the current user. # User Properties Source: https://docs.spikeapi.com/api-reference/user-properties get /userproperties Get information about the current user. # Lab Reports Asynchronous Processing Source: https://docs.spikeapi.com/lab-reports/async Handle lab report analysis with asynchronous processing and real-time webhook notifications ## Asynchronous Processing Overview ```mermaid theme={null} %%{init: {"theme": "base", "themeVariables": {"primaryColor": "#ffffff", "primaryTextColor": "#161a1d", "primaryBorderColor": "#ded1e4", "lineColor": "#006ad4", "secondaryColor": "#f2f6f9", "tertiaryColor": "#eef1f4", "background": "#ffffff", "mainBkg": "#ffffff", "secondBkg": "#f2f6f9", "actorBkg": "#eef1f4", "actorTextColor": "#161a1d", "actorLineColor": "#006ad4", "signalColor": "#006ad4", "signalTextColor": "#161a1d", "labelBoxBkgColor": "#ffffff", "labelTextColor": "#4f5356", "loopTextColor": "#4f5356", "noteBkgColor": "#f2f6f9", "noteTextColor": "#4f5356", "activationBkgColor": "#ded1e4", "activationBorderColor": "#9ea1a5"}}}%% sequenceDiagram participant A as App participant S as Spike API A->>S: POST /auth/hmac
HMAC signature activate S S-->>A: Access Token deactivate S A->>S: POST /lab_reports
Base64 image + token (wait_on_process: false) activate S activate S S-->>A: 200 OK
record_id, status: "pending" deactivate S S->>+A: POST webhook URL
Complete lab report data deactivate S A-->>-S: 200 OK ``` Asynchronous processing allows you to upload lab report documents and receive immediate responses while the AI analysis happens in the background. This approach provides better user experience for interactive applications and enables you to handle multiple requests efficiently. ## How Asynchronous Processing Works 1. **Upload Document** — send a POST request with `wait_on_process: false` (default) 2. **Immediate Response** — receive a response with `status: "processing"`, and a `record_id` 3. **Background Processing** — AI models analyze the document (typically 10–60 seconds) 4. **Get Results** — receive webhook notification with an analysis report when finished ## Getting Results ### Webhooks Configure a webhook URL to receive real-time notifications when analysis completes: **Request:** ```json theme={null} { "mime_type": "application/pdf", "body": "base64-encoded-document-data", "filename": "lab_report_2024_01_15.pdf", "wait_on_process": false } ``` **Immediate Response:** ```json theme={null} { "lab_report": { "record_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "status": "processing", "uploaded_at": "2024-01-15T10:30:04.521Z" } } ``` **Webhook Notification** (when analysis completes): ```json theme={null} { "application_user_id": "end_user_id1", "record_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "status": "completed", "collection_date": "2024-01-15", "result_date": "2024-01-16", "sections": [ { "loinc_code": "58410-2", "loinc_common_name": "CBC panel - Blood by Automated count", "original_section_name": "HEMATOLOGY", "results": [ { "loinc_code": "6690-2", "loinc_common_name": "Leukocytes [#/volume] in Blood", "original_test_name": "White Blood Count", "original_unit_name": "K/uL", "standard_unit_name": "K/uL", "value": 7.2, "within_normal_range": true } ] } ], "uploaded_at": "2024-01-15T10:30:04.521Z", "modified_at": "2024-01-15T10:30:12.132Z" } ``` ### Alternative: Manual Checking If webhooks are not available in your environment, you can check the status using the [`GET /lab_reports/{lab_report_id}`](/api-reference/lab-reports-get-lab-report) endpoint: ``` GET /lab_reports/6ba7b810-9dad-11d1-80b4-00c04fd430c8 ``` ## Webhook Configuration ### Setup Configure your webhook URL through the [admin console](https://admin.spikeapi.com/). Your endpoint must: * Respond with HTTP 200 to acknowledge receipt * Handle POST requests with JSON payloads * Verify HMAC signatures for security If requests fail, the system retries **up to 5 times** with exponential backoff. ### Security All webhook requests include HMAC SHA256 signature verification: * **Header** — `x-body-signature` contains the hex-encoded HMAC signature * **Key** — uses your application's webhook signature key (configured in the admin console) * **Algorithm** — `HMAC-SHA256(webhook_signature_key, request_body)` See [Webhook Signature](/api-docs/webhooks) for more details. ### Status Handling Your webhook handler should handle different status values: * **`completed`** — analysis successful, lab report data are available * **`failed`** — analysis failed, check `parsing_error` field for details When `status` is `failed`, check the `parsing_error` field for specific details about what prevented successful analysis. ## Implementation Examples ```go [Go] theme={null} package main import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "io" "net/http" "time" ) const webhookSignatureKey = "YOUR_WEBHOOK_SECRET_FROM_ADMIN_CONSOLE" // LabReport represents the webhook payload structure type LabReport struct { ApplicationUserID string `json:"application_user_id"` RecordID string `json:"record_id"` Status string `json:"status"` ParsingError string `json:"parsing_error,omitempty"` CollectionDate string `json:"collection_date,omitempty"` UploadedAt time.Time `json:"uploaded_at"` ModifiedAt time.Time `json:"modified_at"` // Add other fields as needed } func main() { http.HandleFunc("/lab-reports-webhook", func(w http.ResponseWriter, r *http.Request) { // Verify HMAC signature signature := r.Header.Get("x-body-signature") if signature == "" { http.Error(w, "Missing signature", http.StatusBadRequest) return } // Read the request body body, err := io.ReadAll(r.Body) if err != nil { http.Error(w, "Failed to read body", http.StatusInternalServerError) return } // Calculate and verify HMAC hm := hmac.New(sha256.New, []byte(webhookSignatureKey)) hm.Write(body) expectedSignature := hex.EncodeToString(hm.Sum(nil)) if signature != expectedSignature { http.Error(w, "Invalid signature", http.StatusUnauthorized) return } // Parse lab report var record LabReport if err := json.Unmarshal(body, &record); err != nil { http.Error(w, "Invalid JSON", http.StatusBadRequest) return } // Process the lab report fmt.Printf("Received lab report analysis: %s for user %s\n", record.Status, record.ApplicationUserID) if record.Status == "completed" { fmt.Printf("Lab report completed for record %s\n", record.RecordID) // Update your application with the results } else if record.Status == "failed" { fmt.Printf("Analysis failed: %s\n", record.ParsingError) // Handle failure case } // Respond with success w.WriteHeader(http.StatusOK) w.Write([]byte("OK")) }) fmt.Println("Starting lab reports webhook server on port 8000") http.ListenAndServe(":8000", nil) } ``` ```javascript [JavaScript] theme={null} const crypto = require("crypto"); const express = require("express"); const WEBHOOK_SIGNATURE_KEY = "YOUR_WEBHOOK_SECRET_FROM_ADMIN_CONSOLE"; const app = express(); // Middleware to capture raw body for signature verification app.use('/lab-reports-webhook', express.raw({type: 'application/json'})); app.post('/lab-reports-webhook', (req, res) => { const signature = req.headers['x-body-signature']; if (!signature) { return res.status(400).send('Missing signature'); } // Verify HMAC signature const hmac = crypto.createHmac('sha256', WEBHOOK_SIGNATURE_KEY); hmac.update(req.body); const expectedSignature = hmac.digest('hex'); if (signature !== expectedSignature) { return res.status(401).send('Invalid signature'); } // Parse lab report try { const labReport = JSON.parse(req.body); console.log(`Received lab report analysis: ${labReport.status} for user ${labReport.application_user_id}`); if (labReport.status === 'completed') { console.log(`Lab report completed: ${labReport.record_id}`); // Update your application with the results updateLabReportData(labReport); } else if (labReport.status === 'failed') { console.log(`Analysis failed: ${labReport.parsing_error}`); // Handle failure case handleAnalysisFailure(labReport.record_id, labReport.application_user_id); } res.status(200).send('OK'); } catch (error) { res.status(400).send('Invalid JSON'); } }); function updateLabReportData(labReport) { // Your application logic to store/process the lab report data console.log('Updating lab report data...'); } function handleAnalysisFailure(recordId, applicationUserId) { // Your application logic to handle failed analysis console.log('Handling analysis failure...'); } app.listen(8000, () => { console.log('Lab reports webhook server running on port 8000'); }); ``` ```python [Python] theme={null} import hmac import hashlib import json from datetime import datetime from flask import Flask, request, Response app = Flask(__name__) WEBHOOK_SIGNATURE_KEY = "YOUR_WEBHOOK_SECRET_FROM_ADMIN_CONSOLE" def verify_signature(body: bytes, signature: str) -> bool: """Verify the HMAC signature of the request body.""" expected_signature = hmac.new( WEBHOOK_SIGNATURE_KEY.encode('utf-8'), body, hashlib.sha256 ).hexdigest() return hmac.compare_digest(signature, expected_signature) @app.route('/lab-reports-webhook', methods=['POST']) def handle_lab_report_webhook(): # Get signature from headers signature = request.headers.get('x-body-signature') if not signature: return Response('Missing signature', status=400) # Get raw request body body = request.get_data() # Verify signature if not verify_signature(body, signature): return Response('Invalid signature', status=401) # Parse lab report try: lab_report = json.loads(body) print(f"Received lab report analysis: {lab_report['status']} for user {lab_report['application_user_id']}") if lab_report['status'] == 'completed': print(f"Lab report completed: {lab_report['record_id']}") # Update your application with the results update_lab_report_data(lab_report) elif lab_report['status'] == 'failed': print(f"Analysis failed: {lab_report.get('parsing_error', 'Unknown error')}") # Handle failure case handle_analysis_failure(lab_report['record_id'], lab_report['application_user_id']) return Response('OK', status=200) except json.JSONDecodeError: return Response('Invalid JSON', status=400) def update_lab_report_data(lab_report): """Update your application with the lab report analysis results.""" print("Updating lab report data...") # Your application logic here def handle_analysis_failure(record_id, application_user_id): """Handle failed lab report analysis.""" print("Handling analysis failure...") # Your application logic here if __name__ == '__main__': print('Starting lab reports webhook server on port 8000') app.run(host='0.0.0.0', port=8000) ``` ```php [PHP] theme={null} updateLabReportData($labReport); } elseif ($labReport['status'] === 'failed') { $parsingError = $labReport['parsing_error'] ?? 'Unknown error'; echo "Analysis failed: {$parsingError}\n"; // Handle failure case $this->handleAnalysisFailure($labReport['record_id'], $labReport['application_user_id']); } http_response_code(200); echo 'OK'; } private function updateLabReportData($labReport) { // Your application logic to store/process the lab report data echo "Updating lab report data...\n"; } private function handleAnalysisFailure($recordId, $applicationUserId) { // Your application logic to handle failed analysis echo "Handling analysis failure...\n"; } } // Handle the webhook request $webhook = new LabReportWebhook(); $webhook->handleRequest(); ?> ``` For complete API specifications and additional configuration options, see the [Implementation Guide](/lab-reports/implementation). # Lab Reports API Implementation Guide Source: https://docs.spikeapi.com/lab-reports/implementation Complete code examples and integration patterns for the Lab Reports API ## About This guide provides complete implementation details and best practices for integrating the Lab Reports API into your applications. For detailed API specifications and data types, refer to the [API Reference](/api-reference/lab-reports-upload-lab-report). **Authentication**: The code examples in this guide focus on the lab report analysis functionality. For authentication implementation details, see the [Authentication documentation](/api-docs/authentication). You'll need to add Bearer token into the Authorization header to all API requests. ## API Endpoints The Lab Reports API provides three main endpoints for managing lab reports: * **[`POST /lab_reports`](/api-reference/lab-reports-upload-lab-report)** — upload lab report documents for AI-powered analysis * **[`GET /lab_reports`](/api-reference/lab-reports-list-lab-reports)** — retrieve a list of lab reports by the time range * **[`GET /lab_reports/{lab_report_id}`](/api-reference/lab-reports-get-lab-report)** — retrieve a specific lab report by ID ## Document Preparation For optimal document capture guidelines, see the [Document Guidelines](/lab-reports/overview#document-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 document in the background. Ideal for user-facing applications where immediate feedback is important. See the [Asynchronous Processing](/lab-reports/async) guide for complete implementation details, webhook configuration, and code examples. #### Synchronous Processing ```mermaid theme={null} %%{init: {"theme": "base", "themeVariables": {"primaryColor": "#ffffff", "primaryTextColor": "#161a1d", "primaryBorderColor": "#ded1e4", "lineColor": "#006ad4", "secondaryColor": "#f2f6f9", "tertiaryColor": "#eef1f4", "background": "#ffffff", "mainBkg": "#ffffff", "secondBkg": "#f2f6f9", "actorBkg": "#eef1f4", "actorTextColor": "#161a1d", "actorLineColor": "#006ad4", "signalColor": "#006ad4", "signalTextColor": "#161a1d", "labelBoxBkgColor": "#ffffff", "labelTextColor": "#4f5356", "loopTextColor": "#4f5356", "noteBkgColor": "#f2f6f9", "noteTextColor": "#4f5356", "activationBkgColor": "#ded1e4", "activationBorderColor": "#9ea1a5"}}}%% sequenceDiagram participant A as App participant S as Spike API A->>+S: POST /auth/hmac
HMAC signature S-->>-A: Access Token A->>+S: POST /lab_reports
Base64 file + token (wait_on_process: true) activate S S-->>A: 200 OK
Complete lab report with status: "completed" deactivate S S->>-A: POST webhook URL
Complete lab report activate A A-->>+S: 200 OK deactivate A ``` Waits for complete analysis before responding. Best for batch processing, server-to-server integrations, or when you can handle longer response times: **Request:** ```json theme={null} { "mime_type": "application/pdf", "body": "base64-encoded-document-data", "filename": "lab_report_2024_01_15.pdf", "wait_on_process": true } ``` **Response:** ```json theme={null} { "lab_report": { "record_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "status": "completed", "collection_date": "2024-01-15", "result_date": "2024-01-16", "sections": [ { "loinc_code": "58410-2", "loinc_common_name": "CBC panel - Blood by Automated count", "original_section_name": "HEMATOLOGY", "results": [ { "loinc_code": "6690-2", "loinc_common_name": "Leukocytes [#/volume] in Blood", "original_test_name": "White Blood Count", "original_unit_name": "K/uL", "standard_unit_name": "K/uL", "value": 7.2, "normal_min": 4.0, "normal_max": 11.0, "within_normal_range": true, "require_human_review": false } ] } ], "uploaded_at": "2024-01-15T10:30:04.521Z", "modified_at": "2024-01-15T10:30:12.132Z" } } ``` ## Response Body ### Processing Status Analysis of the lab report progresses through these states: * **pending** — analysis has been queued * **processing** — an AI model is actively analyzing the document * **completed** — analysis finished successfully with results * **failed** — processing failed due to unreadable content or technical issues REST API responses and webhook notifications will always include the `status` field. ```json theme={null} { "record_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "status": "processing", "uploaded_at": "2024-01-15T10:30:04.521Z" } ``` 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 `parsing_error` field for specific details: * **Document not a lab report** — a document contains nonmedical content * **Poor document quality** — blurry, dark, or low-resolution images/scans * **Unreadable text** — OCR could not extract reliable text * **AI processing timeouts** — although AI providers typically have fast [Response Times](/lab-reports/overview#response-times), processing may occasionally take longer than expected due to reasons that cannot be foreseen. To manage this, the upload process will time out if synchronous processing takes longer than 3 minutes and asynchronous processing takes longer than 5 minutes. #### Request Errors These occur when there's an issue with the request itself, returning non-200 HTTP status codes before analysis begins. * **Decoding File Error** — if the file is corrupted or improperly encoded, and therefore cannot be decoded, then HTTP 400 will be returned. ```json theme={null} { "title": "Bad Request", "status": 400, "detail": "invalid base64 body" } ``` * **Invalid File Format** — Lab Reports supports JPEG, PNG, GIF, WebP, and PDF formats. When a file format is not supported, HTTP status code 400 will be returned. ```json theme={null} { "title": "Bad Request", "status": 400, "detail": "unsupported mime type: application/vnd.openxmlformats-officedocument.wordprocessingml.document" } ``` For HTTP status code errors (400, 401, 422, etc.) and general API error handling, see the [Error Handling documentation](/api-docs/errors). ## Retrieving Results ### Getting Results For asynchronous processing, use webhooks for real-time notifications. See the [Asynchronous Processing Guide](/lab-reports/async) for complete webhook implementation details. If webhooks are not yet available, you can check status using the [`GET /lab_reports/{lab_report_id}`](/api-reference/lab-reports-get-lab-report) endpoint. Retrospectively, a list of all uploaded lab reports can be retrieved using the [`GET /lab_reports`](/api-reference/lab-reports-list-lab-reports) endpoint by providing the time range. ## 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 ### 3. Optimize Document Upload * Use high-resolution scans (300+ DPI) for better OCR results * Validate document size and format before upload * Show upload progress for large documents ## Code Examples Here are complete implementation examples in multiple programming languages: ```python [Python] theme={null} 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 LabReportsAPI: 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 Lab Reports API request header: {auth_error}") raise def upload_lab_report(self, document_path, **options): """Upload a lab report document for analysis""" # Read and encode document try: with open(document_path, 'rb') as doc_file: document_data = base64.b64encode(doc_file.read()).decode('utf-8') except FileNotFoundError: print(f"Document file not found: {document_path}") return None # Prepare the request body body = { "body": document_data, "mime_type": options.get("mime_type", "application/pdf"), "filename": options.get("filename", "lab_report.pdf"), "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}/lab_reports", headers=headers, data=body_json ) if response.status_code == 200: return response.json() print(f"Response status: {response.status_code}") return None # Example usage user_id = "lab_reports_test_user" spike_auth = SpikeAuth(user_id) api = LabReportsAPI(spike_auth) # Upload document asynchronously (recommended) result = api.upload_lab_report("/path/to/lab_report.pdf", wait_on_process=False) if result: print(f"Status: {result['lab_report']['status']}") print(f"Record ID: {result['lab_report']['record_id']}") # For synchronous processing (wait for results) result = api.upload_lab_report("/path/to/lab_report.pdf", wait_on_process=True) if result and result['lab_report']['status'] == 'completed': lab_report = result['lab_report'] print(f"Analysis completed for {lab_report['record_id']}") print(f"Collection Date: {lab_report.get('collection_date', 'N/A')}") print(f"Result Date: {lab_report.get('result_date', 'N/A')}") if lab_report.get('sections'): print(f"Found {len(lab_report['sections'])} sections") for section in lab_report['sections']: print(f"- Section: {section.get('original_section_name', 'Unknown')}") for result in section.get('results', []): value = result.get('value') or result.get('value_text', 'N/A') unit = result.get('original_unit_name', '') normal_status = "" if result.get('within_normal_range') is not None: normal_status = " (Normal)" if result['within_normal_range'] else " (Abnormal)" print(f" + {result.get('original_test_name')}: {value} {unit}{normal_status}") ``` ```javascript [JavaScript] theme={null} const fs = require('fs'); const axios = require('axios'); class LabReportsAPI { constructor(applicationId, hmacKey, baseUrl = 'https://api.spikeapi.com') { this.applicationId = applicationId; this.hmacKey = hmacKey; this.baseUrl = baseUrl; this.accessToken = null; } async createAuthHeaders() { // Create authentication headers - see /api-docs/authentication for details if (!this.accessToken) { throw new Error('Access token not available. Call authenticate() first.'); } return { 'Authorization': `Bearer ${this.accessToken}`, 'Content-Type': 'application/json' }; } async authenticate(userId) { const crypto = require('crypto'); // Generate HMAC signature const hmac = crypto.createHmac('sha256', this.hmacKey); hmac.update(userId); const signature = hmac.digest('hex'); // Exchange signature for access token const response = await axios.post(`${this.baseUrl}/auth/hmac`, { application_id: this.applicationId, application_user_id: userId, signature: signature }); this.accessToken = response.data.access_token; return this.accessToken; } async uploadLabReport(documentPath, options = {}) { // Read and encode document const documentBuffer = fs.readFileSync(documentPath); const documentBase64 = documentBuffer.toString('base64'); // Prepare the request body const body = { body: documentBase64, mime_type: options.mimeType || 'application/pdf', filename: options.filename || 'lab_report.pdf', wait_on_process: options.waitOnProcess || false }; const headers = await this.createAuthHeaders(); // Make request try { const response = await axios.post( `${this.baseUrl}/lab_reports`, body, { headers } ); return response.data; } catch (error) { throw new Error(`API request failed: ${error.response?.data?.message || error.message}`); } } } // Example usage async function analyzeLabReport() { const api = new LabReportsAPI( 9999, // your application ID 'HMAC_KEY_FROM_ADMIN_CONSOLE' // your HMAC key ); try { // Authenticate first await api.authenticate('my_application_user_123'); // your user ID // Asynchronous analysis (recommended) const result = await api.uploadLabReport('path/to/lab-report.pdf', { waitOnProcess: false }); console.log(`Analysis started. Record ID: ${result.lab_report.record_id}`); console.log(`Status: ${result.lab_report.status}`); // Synchronous analysis (wait for results) const syncResult = await api.uploadLabReport('path/to/lab-report.pdf', { waitOnProcess: true }); if (syncResult.lab_report.status === 'completed') { const labReport = syncResult.lab_report; console.log(`Collection Date: ${labReport.collection_date || 'N/A'}`); console.log(`Result Date: ${labReport.result_date || 'N/A'}`); labReport.sections?.forEach(section => { console.log(`Section: ${section.original_section_name}`); section.results?.forEach(result => { const value = result.value || result.value_text || 'N/A'; const unit = result.original_unit_name || ''; const normalStatus = result.within_normal_range !== null ? (result.within_normal_range ? ' (Normal)' : ' (Abnormal)') : ''; console.log(`- ${result.original_test_name}: ${value} ${unit}${normalStatus}`); }); }); } } catch (error) { console.error('Error:', error.message); } } analyzeLabReport(); ``` ```go [Go] theme={null} package main import ( "bytes" "crypto/hmac" "crypto/sha256" "encoding/base64" "encoding/hex" "encoding/json" "fmt" "io" "net/http" "os" "time" ) type LabReportsAPI struct { BaseURL string ApplicationID int64 HMACKey string AccessToken string } type UploadRequest struct { Body string `json:"body"` MimeType string `json:"mime_type"` Filename string `json:"filename"` WaitOnProcess bool `json:"wait_on_process"` } type LabReportResponse struct { LabReport LabReport `json:"lab_report"` } type LabReport struct { RecordID string `json:"record_id"` Status string `json:"status"` CollectionDate string `json:"collection_date"` ResultDate string `json:"result_date"` Sections []LabSection `json:"sections"` UploadedAt time.Time `json:"uploaded_at"` } type LabSection struct { LoincCode string `json:"loinc_code"` LoincCommonName string `json:"loinc_common_name"` OriginalSectionName string `json:"original_section_name"` Results []LabResult `json:"results"` } type LabResult struct { LoincCode string `json:"loinc_code"` LoincCommonName string `json:"loinc_common_name"` OriginalTestName string `json:"original_test_name"` OriginalUnitName string `json:"original_unit_name"` StandardUnitName string `json:"standard_unit_name"` Value *float64 `json:"value,omitempty"` ValueText *string `json:"value_text,omitempty"` WithinNormalRange *bool `json:"within_normal_range,omitempty"` NormalMin *float64 `json:"normal_min,omitempty"` NormalMax *float64 `json:"normal_max,omitempty"` RequireHumanReview bool `json:"require_human_review,omitempty"` } func NewLabReportsAPI(applicationID int64, hmacKey string) *LabReportsAPI { return &LabReportsAPI{ BaseURL: "https://api.spikeapi.com", ApplicationID: applicationID, HMACKey: hmacKey, } } func (api *LabReportsAPI) Authenticate(userID string) error { // Generate HMAC signature h := hmac.New(sha256.New, []byte(api.HMACKey)) h.Write([]byte(userID)) signature := hex.EncodeToString(h.Sum(nil)) // Prepare authentication request authBody := map[string]interface{}{ "application_id": api.ApplicationID, "application_user_id": userID, "signature": signature, } bodyBytes, err := json.Marshal(authBody) if err != nil { return fmt.Errorf("failed to marshal auth request: %w", err) } // Make an authentication request req, err := http.NewRequest("POST", api.BaseURL+"/auth/hmac", bytes.NewBuffer(bodyBytes)) if err != nil { return fmt.Errorf("failed to create auth request: %w", err) } req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") client := &http.Client{Timeout: 30 * time.Second} resp, err := client.Do(req) if err != nil { return fmt.Errorf("auth request failed: %w", err) } defer resp.Body.Close() if resp.StatusCode != 200 { return fmt.Errorf("authentication failed with status %d", resp.StatusCode) } // Parse response respBody, err := io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("failed to read auth response: %w", err) } var authResp map[string]string if err := json.Unmarshal(respBody, &authResp); err != nil { return fmt.Errorf("failed to parse auth response: %w", err) } api.AccessToken = authResp["access_token"] return nil } func (api *LabReportsAPI) createAuthHeaders() map[string]string { if api.AccessToken == "" { panic("Access token not available. Call Authenticate() first.") } return map[string]string{ "Content-Type": "application/json", "Authorization": "Bearer " + api.AccessToken, } } func (api *LabReportsAPI) UploadLabReport(documentPath string, options UploadRequest) (*LabReportResponse, error) { // Read and encode document documentData, err := os.ReadFile(documentPath) if err != nil { return nil, fmt.Errorf("failed to read document: %w", err) } options.Body = base64.StdEncoding.EncodeToString(documentData) // Set defaults if options.MimeType == "" { options.MimeType = "application/pdf" } if options.Filename == "" { options.Filename = "lab_report.pdf" } // Create a request body bodyBytes, err := json.Marshal(options) if err != nil { return nil, fmt.Errorf("failed to marshal request: %w", err) } // Create request req, err := http.NewRequest("POST", api.BaseURL+"/lab_reports", bytes.NewBuffer(bodyBytes)) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } // Set headers authHeaders := api.createAuthHeaders() for key, value := range authHeaders { req.Header.Set(key, value) } // Make request client := &http.Client{Timeout: 60 * time.Second} resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("request failed: %w", err) } defer resp.Body.Close() // Parse response respBody, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response: %w", err) } var result LabReportResponse if err := json.Unmarshal(respBody, &result); err != nil { return nil, fmt.Errorf("failed to parse response: %w", err) } return &result, nil } func main() { api := NewLabReportsAPI(9999, "HMAC_KEY_FROM_ADMIN_CONSOLE") // Authenticate first err := api.Authenticate("my_application_user_123") if err != nil { fmt.Printf("Authentication failed: %v\n", err) return } // Asynchronous analysis result, err := api.UploadLabReport("path/to/lab-report.pdf", UploadRequest{ WaitOnProcess: false, }) if err != nil { fmt.Printf("Error: %v\n", err) return } fmt.Printf("Analysis started. Record ID: %s\n", result.LabReport.RecordID) fmt.Printf("Status: %s\n", result.LabReport.Status) // Synchronous analysis syncResult, err := api.UploadLabReport("path/to/lab-report.pdf", UploadRequest{ WaitOnProcess: true, }) if err != nil { fmt.Printf("Error: %v\n", err) return } if syncResult.LabReport.Status == "completed" { labReport := syncResult.LabReport fmt.Printf("Collection Date: %s\n", labReport.CollectionDate) fmt.Printf("Result Date: %s\n", labReport.ResultDate) for _, section := range labReport.Sections { fmt.Printf("Section: %s\n", section.OriginalSectionName) for _, result := range section.Results { value := "N/A" if result.Value != nil { value = fmt.Sprintf("%.2f", *result.Value) } else if result.ValueText != nil { value = *result.ValueText } fmt.Printf("- %s: %s", result.OriginalTestName, value) if result.OriginalUnitName != "" { fmt.Printf(" %s", result.OriginalUnitName) } if result.WithinNormalRange != nil { if *result.WithinNormalRange { fmt.Printf(" (Normal)") } else { fmt.Printf(" (Abnormal)") } } fmt.Printf("\n") } } } } ``` For complete API specification, data types, and additional parameters, see the [`POST /lab_reports`](/api-reference/lab-reports-upload-lab-report) API Reference. # Lab Reports Overview Source: https://docs.spikeapi.com/lab-reports/overview Upload lab report documents and receive detailed structured analysis powered by AI. ## About The Lab Reports API uses advanced OCR and natural language processing to extract test results, map to LOINC terminology, and provide comprehensive medical data while maintaining HIPAA compliance. ## Getting Started 1. Get an access token using the authentication flow (see [Authentication](/api-docs/authentication)) 2. Upload base64-encoded lab report documents using the POST endpoint 3. Retrieve results either synchronously or via webhook notifications ## Key Features ### AI-Powered Analysis * Advanced OCR for PDF and image document processing * LOINC standard terminology for precise test result classification * Machine learning models for accurate data extraction and validation ### Flexible Processing * **Asynchronous processing** (recommended) — returns immediately with background processing * **Synchronous processing** — waits for complete analysis before returning results * **Webhook notifications** — for real-time status updates ### Comprehensive Data * **LOINC code mapping** for standardized test identification * **Structured sections** with organized test results * **HIPAA compliant** automatic de-identification * **Quality validation** with human review flags for uncertain results ## Technical Requirements ### Document Specifications * **Maximum size** — 10MB when base64-encoded * **Supported formats** — PDF (preferred), JPEG, PNG, GIF, WEBP * **Input format** — base64-encoded string * **Quality** — high resolution (300+ DPI) recommended for best OCR results ### Processing Workflow 1. **Document upload & validation** — a system validates format compatibility 2. **OCR processing** — extract text from PDF or image documents 3. **AI analysis** — parse sections and identify individual test results 4. **LOINC mapping** — map test names to standard LOINC codes when possible 5. **Validation** — flag uncertain results for human review 6. **Anonymization** — remove sensitive patient information per HIPAA guidelines 7. **Storage** — results stored with a configurable retention policy ## Analysis Report Data ### Main Data * Collection and result dates * Original and standardized section names * Test-specific notes and flags * Processing status and error details ### Test Results Structure * Original test names from the lab report * LOINC codes and common names when available * Numeric values with units or text values * Normal range indicators * Human review requirements ### LOINC Integration * **Sections** — mapped to panel-level LOINC codes (e.g., "58410-2" for CBC panel) * **Individual Tests** — specific LOINC codes for each test (e.g., "6690–2" for WBC count) * **Quality Assurance** — only confident mappings are included; uncertain mappings are flagged for review ## Document Guidelines For optimal analysis results, ensure lab reports: 1. **High quality** — use clear, high-resolution scans (300+ DPI) 2. **Complete pages** — include all pages of multipage reports 3. **Proper orientation** — ensure documents are right-side up 4. **Good contrast** — text should be clearly readable against a background 5. **Standard formats** — PDF preferred for text-based reports, high-quality images for scanned documents 6. **Remove artifacts** — avoid shadows, folds, or obstructions that might interfere with OCR ## Implementation For detailed implementation examples, request/response schemas, and code samples, see: * **[Implementation Guide](/lab-reports/implementation)** — complete code examples and integration patterns * **[Asynchronous Processing](/lab-reports/async)** — real-time notifications and webhook implementation * **[API Reference](/api-reference/lab-reports-upload-lab-report)** — detailed endpoint specifications and data types ### LOINC Code Support The API supports mapping to standard [LOINC](https://loinc.org/) terminology for both sections and individual test results. By default, mappings are only included when the confidence level is high to ensure accuracy. Uncertain mappings are flagged with `require_human_review: true` for manual verification. LOINC codes have the [structure](https://loinc.org/kb/faq/structure/) of 3–10 digits with the check digit preceded by a hyphen (e.g., "6690–2"). Each code is accompanied by a corresponding common name. ## Response Times Response times typically range from 10 to 60 seconds depending on document complexity, quality, and the number of test results. Processing times may vary due to OCR complexity and the depth of LOINC mapping analysis. You may use asynchronous processing for a better user experience during longer processing times. # Desktop Chat Clients Source: https://docs.spikeapi.com/mcp-docs/desktop_clients Local testing setup for exploring Spike MCP capabilities through desktop chat clients. This guide is for **local testing and experimentation only**. For production integrations, see the [Implementation Guide](/mcp-docs/implementation) for complete examples with OpenAI and Anthropic. Desktop chat clients like Claude Desktop provide an easy way to test MCP capabilities and experiment with health data queries locally. ## Claude Desktop Setup Download Claude Desktop from [https://claude.ai/download](https://claude.ai/download). ### Step 1: Install MCP Proxy Install the open-source MCP proxy tool: ```bash theme={null} brew install mcp-proxy ``` ### Step 2: Configure Claude Desktop 1. Open Claude Desktop and navigate to **Settings** → **Developer** 2. Click the button to edit the configuration file Claude config menus 3. Add the following configuration to the file (replace `` with your actual token): ```json theme={null} { "mcpServers": { "mcp-proxy": { "command": "mcp-proxy", "args": [ "-H", "Authorization", "Bearer ", "--transport", "streamablehttp", "https://app-api.spikeapi.com/v3/mcp" ] } } } ``` 4. Save the file and restart Claude Desktop ### Step 3: Test the Connection Try a simple query to verify everything is working: ``` How well did I sleep last night? ``` You should see the `query_sleep` tool being used and may be prompted for approval: Claude config menus Allow the connection to get personalized results based on your JWT token: Claude config menus ## Next Steps For production integrations and complete implementation examples, see the [Implementation Guide](/mcp-docs/implementation) which includes: * Complete OpenAI integration examples (Python, Go, Node.js, cURL) * Anthropic/Claude integration examples * Authentication setup * Error handling * Available MCP tools and endpoints # Integration Guide Source: https://docs.spikeapi.com/mcp-docs/implementation Complete implementation examples for integrating Spike's MCP server with AI providers Integrate Spike's health data into AI applications using the Model Context Protocol (MCP). The examples below show complete implementations for OpenAI and Anthropic clients, including authentication, server configuration, and data querying. **Requirements:** * Spike JWT access token (see [Authentication](/api-docs/authentication)) * AI provider API key (OpenAI or Anthropic) * MCP server URL: `https://app-api.spikeapi.com/v3/mcp` ```mermaid theme={null} sequenceDiagram participant UserApp as User Application participant Provider as AI Provider (e.g., OpenAI/Anthropic) participant MCP as MCP Server (Spike Health Data) UserApp->>UserApp: Load OPENAI_API_KEY/\nSPIKE_ACCESS_TOKEN UserApp->>Provider: Authenticate with API Key UserApp->>MCP: Configure MCP tool (JWT, URL)\nset as available tool for Provider UserApp->>Provider: Request health data analysis\n(e.g., Analyze my sleep data...) Provider->>MCP: API request with Authorization: Bearer SPIKE_ACCESS_TOKEN MCP-->>Provider: User health data analysis/insights Provider-->>UserApp: Return analysis, tokens used, etc. Note over UserApp,Provider: Error handling if missing\nAPI Key or Token ``` ### Using the Token Include the JWT token in the Authorization header when configuring the MCP tool: ``` Authorization: Bearer ``` ## OpenAI ```bash [Curl] theme={null} curl -X POST "https://api.openai.com/v1/responses" \ -H "Authorization: Bearer OPENAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "o4-mini", "instructions": "you are a health and wellness analyst.", "input": "Analyze my health data for 2025-08-17 and provide a summary of my health data.", "tools": [ { "type": "mcp", "server_label": "spike-health-data", "server_url": "https://app-api.spikeapi.com/v3/mcp", "headers": { "Authorization": "Bearer SPIKE_ACCESS_TOKEN" }, "server_description": "Health and fitness data analysis server providing daily and hourly statistics from connected wearables and health devices.", "require_approval": "never" } ], "tool_choice": "auto", "max_tool_calls": 1, "max_output_tokens": 5000, "parallel_tool_calls": true, "metadata": { "analysis_type": "daily_health_review", "date": "2025-08-17", "tools_used": "mcp_health_data", "version": "1.0.0" } }' ``` ```go [Go] theme={null} package main import ( "context" "fmt" "log" "os" openai "github.com/openai/openai-go/v2" "github.com/openai/openai-go/v2/option" "github.com/openai/openai-go/v2/responses" ) func main() { // Required environment variables openaiKey := os.Getenv("OPENAI_API_KEY") spikeToken := os.Getenv("SPIKE_ACCESS_TOKEN") if openaiKey == "" || spikeToken == "" { log.Fatal("OPENAI_API_KEY and SPIKE_ACCESS_TOKEN environment variables are required") } // Create OpenAI client client := openai.NewClient(option.WithAPIKey(openaiKey)) // Configure the SpikeAI MCP tool mcpTool := responses.ToolUnionParam{ OfMcp: &responses.ToolMcpParam{ Type: "mcp", ServerLabel: "spike-health-data", ServerURL: "https://app-api.spikeapi.com/v3/mcp", Headers: map[string]string{ "Authorization": fmt.Sprintf("Bearer %s", spikeToken), }, ServerDescription: openai.String("Health and fitness data analysis server"), RequireApproval: responses.ToolMcpRequireApprovalUnionParam{ OfMcpToolApprovalSetting: openai.String("never"), }, }, } // Create the chat request request := responses.ResponseNewParams{ Model: responses.ChatModelO4Mini, Instructions: openai.String("You are a health data analyst. Use the available tools to analyze the user's health data and provide insights."), Input: responses.ResponseNewParamsInputUnion{ OfString: openai.String("Analyze my sleep data for the past 3 days and give me a brief summary of my sleep patterns."), }, Tools: []responses.ToolUnionParam{mcpTool}, ToolChoice: responses.ResponseNewParamsToolChoiceUnion{ OfToolChoiceMode: openai.Opt(responses.ToolChoiceOptionsAuto), }, MaxOutputTokens: openai.Opt(int64(1000)), MaxToolCalls: openai.Opt(int64(3)), ParallelToolCalls: openai.Opt(true), } // Make the API call ctx := context.Background() response, err := client.Responses.New(ctx, request) if err != nil { log.Fatalf("API call failed: %v", err) } // Print the response fmt.Println("=== Health Data Analysis ===") fmt.Println(response.OutputText()) // Print usage statistics fmt.Printf("\nTokens used - Input: %d, Output: %d, Total: %d\n", response.Usage.InputTokens, response.Usage.OutputTokens, response.Usage.TotalTokens) } ``` ```python [Python] theme={null} import os import sys from openai import OpenAI def main(): # Required environment variables openai_key = os.getenv("OPENAI_API_KEY") spike_token = os.getenv("SPIKE_ACCESS_TOKEN") if not openai_key or not spike_token: print("Error: OPENAI_API_KEY and SPIKE_ACCESS_TOKEN environment variables are required") sys.exit(1) # Create OpenAI client client = OpenAI(api_key=openai_key) # Configure the SpikeAI MCP tool mcp_tool = { "type": "mcp", "server_label": "spike-health-data", "server_url": "https://app-api.spikeapi.com/v3/mcp", "headers": { "Authorization": f"Bearer {spike_token}" }, "server_description": "Health and fitness data analysis server", "require_approval": "never" } try: # Create the responses request response = client.responses.create( model="gpt-4o", input=[ { "role": "user", "content": "Analyze my sleep data for the past 3 days and give me a brief summary of my sleep patterns." } ], instructions="You are a health data analyst. Use the available tools to analyze the user's health data and provide insights.", tools=[mcp_tool], max_tokens=1000 ) # Print the response print("=== Health Data Analysis ===") print(response.output[0].content) # Print usage statistics if available if hasattr(response, 'usage'): usage = response.usage print(f"\nTokens used - Input: {usage.prompt_tokens}, Output: {usage.completion_tokens}, Total: {usage.total_tokens}") except Exception as e: print(f"API call failed: {e}") sys.exit(1) if __name__ == "__main__": main() ``` ```typescript [Node.js] theme={null} const openaiKey = process.env.OPENAI_API_KEY; const spikeToken = process.env.SPIKE_ACCESS_TOKEN; if (!openaiKey || !spikeToken) { console.error('Error: OPENAI_API_KEY and SPIKE_ACCESS_TOKEN environment variables are required'); process.exit(1); } // Create OpenAI client const client = new OpenAI({ apiKey: openaiKey }); // Configure the SpikeAI MCP tool const mcpTool = { type: 'mcp', server_label: 'spike-health-data', server_url: 'https://app-api.spikeapi.com/v3/mcp', headers: { 'Authorization': `Bearer ${spikeToken}` }, server_description: 'Health and fitness data analysis server', require_approval: 'never' }; try { // Create the response request using the new responses API const response = await client.responses.create({ model: 'o1-mini', instructions: 'You are a health data analyst. Use the available tools to analyze the user\'s health data and provide insights.', input: 'Analyze my sleep data for the past 3 days and give me a brief summary of my sleep patterns.', tools: [mcpTool], tool_choice: 'auto', max_output_tokens: 1000, max_tool_calls: 3, parallel_tool_calls: true }); // Print the response console.log('=== Health Data Analysis ==='); console.log(response.output_text); // Print usage statistics const usage = response.usage; console.log(`\nTokens used - Input: ${usage.input_tokens}, Output: ${usage.output_tokens}, Total: ${usage.total_tokens}`); } catch (error) { console.error(`API call failed: ${error.message}`); process.exit(1); } ``` ## Anthropic ```bash [Curl] theme={null} curl https://api.anthropic.com/v1/messages \ -H "Content-Type: application/json" \ -H "X-API-Key: $ANTHROPIC_API_KEY" \ -H "anthropic-version: 2023-06-01" \ -H "anthropic-beta: mcp-client-2025-04-04" \ -d '{ "model": "claude-sonnet-4-20250514", "max_tokens": 1024, "mcp_servers": [ { "type": "url", "name": "spike-health-data", "url": "https://app-api.spikeapi.com/v3/mcp", "authorization_token": "SPIKE_ACCESS_TOKEN", "tool_configuration": { "enabled": true, } } ], "messages": [ { "role": "user", "content": "Analyze my health data for 2025-08-17 and provide a summary of my health data." } ] }' ``` ```python [Python] theme={null} import os import sys from anthropic import Anthropic def main(): # Required environment variables anthropic_key = os.getenv("ANTHROPIC_API_KEY") spike_token = os.getenv("SPIKE_ACCESS_TOKEN") if not anthropic_key or not spike_token: print("Error: ANTHROPIC_API_KEY and SPIKE_ACCESS_TOKEN environment variables are required") sys.exit(1) # Create Anthropic client client = Anthropic(api_key=anthropic_key) try: # Create the message request using MCP server response = client.beta.messages.create( model="claude-3-5-sonnet-20241022", max_tokens=1000, messages=[ { "role": "user", "content": "You are a health data analyst. Analyze my sleep data for the past 3 days and give me a brief summary of my sleep patterns.", } ], mcp_servers=[ { "type": "url", "url": "https://app-api.spikeapi.com/v3/mcp", "authorization_token": spike_token, "name": "spike-health-data", "tool_configuration": { "enabled": True, }, } ], extra_headers={ "anthropic-beta": "mcp-client-2025-04-04", }, ) # Print the response print("=== Health Data Analysis ===") for content_block in response.content: if content_block.type == "text": print(content_block.text) # Print usage statistics usage = response.usage print(f"\nTokens used - Input: {usage.input_tokens}, Output: {usage.output_tokens}") except Exception as e: print(f"API call failed: {e}") sys.exit(1) if __name__ == "__main__": main() ``` # MCP Overview Source: https://docs.spikeapi.com/mcp-docs/overview Overview of the Spike MCP server and how to use it ## What is MCP? The [Model Context Protocol (MCP)](https://modelcontextprotocol.io/docs/getting-started/intro) is an open standard that enables AI applications to securely connect to external data sources and tools. Think of it as a bridge that allows AI models like ChatGPT, Claude, or your custom AI application to access real-world data and perform actions on your behalf. ## Spike's MCP Server Spike provides a ready-to-use MCP server that makes health and fitness data accessible to AI applications. This means you can ask AI assistants natural language questions about your health data, and they can automatically fetch, analyze, and interpret information from your connected wearables and health devices. The MCP server handles authentication, data retrieval, and formatting, so AI applications can focus on analysis and user interaction rather than API integration complexity. For more information about how to use the MCP server, see the [Implementation Guide](/mcp-docs/implementation). For a list of available MCP tools, see the [Tools](/mcp-docs/tools) page. ## Common Use Cases ### Personal Health Analysis Ask AI assistants to analyze your health trends and provide insights: * *"How has my sleep quality changed over the past month?"* * *"Compare my activity levels between weekdays and weekends"* * *"What patterns do you see in my heart rate data?"* ### Health Coaching & Recommendations Get personalized recommendations based on your actual data: * *"Based on my recent sleep patterns, what should I focus on to improve my rest?"* * *"Suggest workout adjustments based on my recovery metrics"* * *"How can I improve my daily step count given my current trends?"* ### Data Correlation & Discovery Uncover relationships between different health metrics: * *"Is there a correlation between my sleep duration and next-day activity levels?"* * *"How does my heart rate variability relate to my stress levels?"* * *"What impact does my workout intensity have on my recovery time?"* ### Health Reporting & Summaries Generate comprehensive health reports automatically: * *"Create a weekly health summary for my doctor's appointment"* * *"Summarize my fitness progress over the last quarter"* * *"Generate a sleep quality report for the past two weeks"* ### User Dataset Integration Combine your own datasets with health metrics for comprehensive analysis: * *"Analyze my symptom diary alongside my heart rate variability and stress levels"* * *"How do my mood entries correlate with my sleep quality and activity levels?"* * *"Compare my productivity journal with my energy levels and recovery metrics"* * *"Identify patterns between my medication timing and heart rate data"* ### Personalized Health Research Conduct your own health studies using combined data sources: * *"Track how my meditation practice affects my HRV and resting heart rate"* * *"Analyze the relationship between my diet log and my sleep duration"* * *"How does my work stress level (from my journal) impact my recovery score?"* * *"Find correlations between my supplement intake and energy levels"* ### Application Integration Build AI-powered features into your health applications: * Automated health insights in mobile apps * Personalized coaching recommendations * Natural language health data queries * Intelligent health trend analysis * User-provided dataset correlation analysis # MCP Tools Source: https://docs.spikeapi.com/mcp-docs/tools Tools available in the MCP server ## Available Tools The MCP server provides access to all Spike API endpoints including sleep data, daily statistics, workouts, and user profiles. Tools are automatically discovered and made available to the AI model. However, you can also manually limit the tools that are available to the AI model for security and optimization purposes ## Tools The MCP server provides the following tools: ### `query_statistics_hourly` Query hourly health and fitness statistics aggregated by hour intervals. Returns hourly aggregated values for activity patterns like steps, distance, calories, heart rate, and sleep duration. ### `query_statistics_daily` Query daily health and fitness statistics aggregated by day. Returns daily aggregated values for health metrics including steps, distance, calories, heart rate, sleep data, and wellness scores. ### `query_sleep` Query sleep data for a specific day. Returns detailed sleep information including sleep stages and duration for the requested date. ### `query_workout` Query workout data for a specific date range. Returns comprehensive workout information with optional detailed breakdowns including sessions, laps, GPS routes, splits, and segments. ### `get_user_info` Get user information including application details, user ID, and integrated providers. Returns account overview and connected health data provider status. ### `get_user_properties` Get user properties including personal health and demographic information. Returns profile data such as timezone, physical measurements, and demographics collected from connected devices. # Nutrition AI Asynchronous Processing Source: https://docs.spikeapi.com/nutrition-ai/async Handle nutrition analysis with asynchronous processing and real-time webhook notifications ## Asynchronous Processing Overview ```mermaid theme={null} %%{init: {"theme": "base", "themeVariables": {"primaryColor": "#ffffff", "primaryTextColor": "#161a1d", "primaryBorderColor": "#ded1e4", "lineColor": "#006ad4", "secondaryColor": "#f2f6f9", "tertiaryColor": "#eef1f4", "background": "#ffffff", "mainBkg": "#ffffff", "secondBkg": "#f2f6f9", "actorBkg": "#eef1f4", "actorTextColor": "#161a1d", "actorLineColor": "#006ad4", "signalColor": "#006ad4", "signalTextColor": "#161a1d", "labelBoxBkgColor": "#ffffff", "labelTextColor": "#4f5356", "loopTextColor": "#4f5356", "noteBkgColor": "#f2f6f9", "noteTextColor": "#4f5356", "activationBkgColor": "#ded1e4", "activationBorderColor": "#9ea1a5"}}}%% sequenceDiagram participant A as App participant S as Spike API A->>S: POST /auth/hmac
HMAC signature activate S S-->>A: Access Token deactivate S A->>S: POST /nutrition_records
Base64 image + token (wait_on_process: false) activate S activate S S-->>A: 200 OK
record_id, status: "pending" deactivate S S->>+A: POST webhook URL
Complete nutrition results deactivate S A-->>-S: 200 OK ``` Asynchronous processing allows you to upload food images and receive immediate responses while the AI analysis happens in the background. This approach provides better user experience for interactive applications and enables you to handle multiple requests efficiently. ## How Asynchronous Processing Works 1. **Upload Image** — send a POST request with `wait_on_process: false` (default) 2. **Immediate Response** — receive a response with `status: "pending"` or `status: "processing"`, and a `record_id` 3. **Background Processing** — AI models analyze the image (typically 4–30 seconds) 4. **Get Results** — receive webhook notification with an analysis report when finished ## Getting Results ### Webhooks Configure a webhook URL to receive real-time notifications when analysis completes: **Request:** ```json theme={null} { "body": "base64-encoded-image-data", "include_ingredients": true, "include_nutri_score": true, "wait_on_process": false } ``` **Immediate Response:** ```json theme={null} { "record_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "status": "processing", "uploaded_at": "2025-09-15T10:30:04.521Z" } ``` **Webhook Notification** (when analysis completes): ```json theme={null} { "application_user_id": "end_user_id1", "record_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "status": "completed", "dish_name": "grilled chicken caesar salad", "dish_description": "chicken breast, grilled with romaine lettuce, parmesan cheese, and caesar dressing", "nutri_score": "B", "serving_size": 275, "unit": "g", "nutritional_fields": { "energy_kcal": 292, "protein_g": 42.2, "fat_total_g": 10.9, "carbohydrate_g": 4.3, "fiber_total_dietary_g": 2.1 }, "ingredients": [ { "name": "chicken breast, grilled", "serving_size": 150.0, "unit": "g", "nutritional_fields": { "energy_kcal": 165, "protein_g": 31, "fat_total_g": 3.6, "carbohydrate_g": 0, "fiber_total_dietary_g": 0 } } ], "uploaded_at": "2025-09-15T10:30:04.521Z", "modified_at": "2025-09-15T10:30:12.132Z", "consumed_at": "2025-09-15T10:30:04Z" } ``` ### Alternative: Manual Checking If webhooks are not available in your environment, you can check the status using the [`GET /nutrition_records/{id}`](/api-reference/nutrition-ai-get-nutrition-record) endpoint: ``` GET /nutrition_records/6ba7b810-9dad-11d1-80b4-00c04fd430c8 ``` ## Webhook Configuration ### Setup Configure your webhook URL through the [admin console](https://admin.spikeapi.com/). Your endpoint must: * Respond with HTTP 200 to acknowledge receipt * Handle POST requests with JSON payloads * Verify HMAC signatures for security If requests fail, the system retries **up to 10 times** with exponential backoff. ### Security All webhook requests include HMAC SHA256 signature verification: * **Header** — `x-body-signature` contains the hex-encoded HMAC signature * **Key** — uses your application's webhook signature key (configured in the admin console) * **Algorithm** — `HMAC-SHA256(webhook_signature_key, request_body)` See [Webhook Signature](/api-docs/webhooks) for more details. ### Status Handling Your webhook handler should handle different status values. For complete status definitions, see [Processing Status](/nutrition-ai/implementation#processing-status). When `status` is `failed`, check the `failure_reason` field for specific details about what prevented successful analysis. ## Best Practices ### 1. Webhook Implementation * **Respond quickly** — always respond with HTTP 200 immediately, then process data asynchronously * **Validate signatures** — always verify the HMAC signature before processing webhook data * **Handle failures gracefully** — check the status field and handle both success and failure cases * **Implement idempotency** — use the `record_id` to avoid processing the same webhook multiple times ### 2. Error Handling * Handle webhook delivery failures gracefully * Store webhook secrets securely and never commit them to version control * Implement retry logic for critical webhook processing ### 3. User Experience * Show immediate feedback when the image is uploaded ("Analysis in progress...") * Provide loading indicators during the processing window * Handle both success and failure states in your UI ## Use Cases Asynchronous processing is ideal for: * **Mobile applications** — immediate feedback while processing happens in the background * **Web applications** — non-blocking user interfaces with real-time updates * **High-volume scenarios** — process multiple images concurrently * **User-facing tools** — food logging apps, dietary tracking applications ## Implementation Examples ```go [Go] theme={null} package main import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "io" "net/http" "time" ) const webhookSignatureKey = "YOUR_WEBHOOK_SECRET_FROM_ADMIN_CONSOLE" // NutritionRecord represents the webhook payload structure type NutritionRecord struct { ApplicationID int64 `json:"application_id"` UID string `json:"uid"` RecordID string `json:"record_id"` Status string `json:"status"` DishName string `json:"dish_name"` NutriScore string `json:"nutri_score"` UploadedAt time.Time `json:"uploaded_at"` ModifiedAt time.Time `json:"modified_at"` // Add other fields as needed } func main() { http.HandleFunc("/nutrition-webhook", func(w http.ResponseWriter, r *http.Request) { // Verify HMAC signature signature := r.Header.Get("x-body-signature") if signature == "" { http.Error(w, "Missing signature", http.StatusBadRequest) return } // Read the request body body, err := io.ReadAll(r.Body) if err != nil { http.Error(w, "Failed to read body", http.StatusInternalServerError) return } // Calculate and verify HMAC hm := hmac.New(sha256.New, []byte(webhookSignatureKey)) hm.Write(body) expectedSignature := hex.EncodeToString(hm.Sum(nil)) if signature != expectedSignature { http.Error(w, "Invalid signature", http.StatusUnauthorized) return } // Parse nutrition record var record NutritionRecord if err := json.Unmarshal(body, &record); err != nil { http.Error(w, "Invalid JSON", http.StatusBadRequest) return } // Process the nutrition record fmt.Printf("Received nutrition analysis: %s for user %s\n", record.Status, record.UID) if record.Status == "completed" { fmt.Printf("Dish: %s, Nutri-Score: %s\n", record.DishName, record.NutriScore) // Update your application with the results } else if record.Status == "failed" { fmt.Printf("Analysis failed for record %s\n", record.RecordID) // Handle failure case } // Respond with success w.WriteHeader(http.StatusOK) w.Write([]byte("OK")) }) fmt.Println("Starting nutrition webhook server on port 8000") http.ListenAndServe(":8000", nil) } ``` ```javascript [JavaScript] theme={null} const crypto = require("crypto"); const express = require("express"); const WEBHOOK_SIGNATURE_KEY = "YOUR_WEBHOOK_SECRET_FROM_ADMIN_CONSOLE"; const app = express(); // Middleware to capture raw body for signature verification app.use('/nutrition-webhook', express.raw({type: 'application/json'})); app.post('/nutrition-webhook', (req, res) => { const signature = req.headers['x-body-signature']; if (!signature) { return res.status(400).send('Missing signature'); } // Verify HMAC signature const hmac = crypto.createHmac('sha256', WEBHOOK_SIGNATURE_KEY); hmac.update(req.body); const expectedSignature = hmac.digest('hex'); if (signature !== expectedSignature) { return res.status(401).send('Invalid signature'); } // Parse nutrition record try { const nutritionRecord = JSON.parse(req.body); console.log(`Received nutrition analysis: ${nutritionRecord.status} for user ${nutritionRecord.uid}`); if (nutritionRecord.status === 'completed') { console.log(`Dish: ${nutritionRecord.dish_name}, Nutri-Score: ${nutritionRecord.nutri_score}`); // Update your application with the results updateUserNutritionData(nutritionRecord); } else if (nutritionRecord.status === 'failed') { console.log(`Analysis failed: ${nutritionRecord.failure_reason}`); // Handle failure case handleAnalysisFailure(nutritionRecord.record_id, nutritionRecord.uid); } res.status(200).send('OK'); } catch (error) { res.status(400).send('Invalid JSON'); } }); function updateUserNutritionData(nutritionRecord) { // Your application logic to store/process the nutrition data console.log('Updating user nutrition data...'); } function handleAnalysisFailure(recordId, uid) { // Your application logic to handle failed analysis console.log('Handling analysis failure...'); } app.listen(8000, () => { console.log('Nutrition webhook server running on port 8000'); }); ``` ```python [Python] theme={null} import hmac import hashlib import json from datetime import datetime from flask import Flask, request, Response app = Flask(__name__) WEBHOOK_SIGNATURE_KEY = "YOUR_WEBHOOK_SECRET_FROM_ADMIN_CONSOLE" def verify_signature(body: bytes, signature: str) -> bool: """Verify the HMAC signature of the request body.""" expected_signature = hmac.new( WEBHOOK_SIGNATURE_KEY.encode('utf-8'), body, hashlib.sha256 ).hexdigest() return hmac.compare_digest(signature, expected_signature) @app.route('/nutrition-webhook', methods=['POST']) def handle_nutrition_webhook(): # Get signature from headers signature = request.headers.get('x-body-signature') if not signature: return Response('Missing signature', status=400) # Get raw request body body = request.get_data() # Verify signature if not verify_signature(body, signature): return Response('Invalid signature', status=401) # Parse nutrition record try: nutrition_record = json.loads(body) print(f"Received nutrition analysis: {nutrition_record['status']} for user {nutrition_record['uid']}") if nutrition_record['status'] == 'completed': print(f"Dish: {nutrition_record.get('dish_name', 'Unknown')}") print(f"Nutri-Score: {nutrition_record.get('nutri_score', 'N/A')}") # Update your application with the results update_user_nutrition_data(nutrition_record) elif nutrition_record['status'] == 'failed': print(f"Analysis failed: {nutrition_record.get('failure_reason', 'Unknown error')}") # Handle failure case handle_analysis_failure(nutrition_record['record_id'], nutrition_record['uid']) return Response('OK', status=200) except json.JSONDecodeError: return Response('Invalid JSON', status=400) def update_user_nutrition_data(nutrition_record): """Update your application with the nutrition analysis results.""" print("Updating user nutrition data...") # Your application logic here def handle_analysis_failure(record_id, uid): """Handle failed nutrition analysis.""" print("Handling analysis failure...") # Your application logic here if __name__ == '__main__': print('Starting nutrition webhook server on port 8000') app.run(host='0.0.0.0', port=8000) ``` ```php [PHP] theme={null} updateUserNutritionData($nutritionRecord); } elseif ($nutritionRecord['status'] === 'failed') { $failureReason = $nutritionRecord['failure_reason'] ?? 'Unknown error'; echo "Analysis failed: {$failureReason}\n"; // Handle failure case $this->handleAnalysisFailure($nutritionRecord['record_id'], $nutritionRecord['uid']); } http_response_code(200); echo 'OK'; } private function updateUserNutritionData($nutritionRecord) { // Your application logic to store/process the nutrition data echo "Updating user nutrition data...\n"; } private function handleAnalysisFailure($recordId, $uid) { // Your application logic to handle failed analysis echo "Handling analysis failure...\n"; } } // Handle the webhook request $webhook = new NutritionWebhook(); $webhook->handleRequest(); ?> ``` For complete API specifications and additional configuration options, see the [Implementation Guide](/nutrition-ai/implementation). # Nutrition AI Implementation Guide Source: https://docs.spikeapi.com/nutrition-ai/implementation Complete code examples and integration patterns for the Nutrition API ## 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](/api-reference/nutrition-ai-analyze-nutrition-image). **Authentication**: The code examples in this guide focus on the nutrition analysis functionality. For authentication implementation details, see the [Authentication documentation](/api-docs/authentication). 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: * **[`POST /nutrition_records/image`](/api-reference/nutrition-ai-analyze-nutrition-image)** — upload a food image for AI-powered nutritional analysis * **[`POST /nutrition_records/ingredients/label`](/api-reference/nutrition-ai-analyze-nutrition-label)** — upload an image of a nutritional facts label to analyze it * **[`POST /nutrition_records/manual`](/api-reference/nutrition-ai-upload-nutrition-record)** — upload a manually created nutrition record * **[`PATCH /nutrition_records/{id}`](/api-reference/nutrition-ai-modify-nutrition-record)** — change the portion size for a nutrition record by ID * **[`PUT /nutrition_records/{id}`](/api-reference/nutrition-ai-replace-nutrition-record)** — replace the nutrition record with a new one * **[`GET /nutrition_records`](/api-reference/nutrition-ai-list-nutrition-records)** — retrieve a list of nutrition records by the time range * **[`GET /nutrition_records/{id}`](/api-reference/nutrition-ai-get-nutrition-record)** — retrieve a specific nutrition record by ID * **[`DELETE /nutrition_records/{id}`](/api-reference/nutrition-ai-delete-nutrition-record)** — delete a specific nutrition record by ID ## Image Preparation For optimal image capture guidelines, see the [Image Guidelines](/nutrition-ai/overview#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](/nutrition-ai/async) guide for complete implementation details, webhook configuration, and code examples. #### Synchronous Processing ```mermaid theme={null} %%{init: {"theme": "base", "themeVariables": {"primaryColor": "#ffffff", "primaryTextColor": "#161a1d", "primaryBorderColor": "#ded1e4", "lineColor": "#006ad4", "secondaryColor": "#f2f6f9", "tertiaryColor": "#eef1f4", "background": "#ffffff", "mainBkg": "#ffffff", "secondBkg": "#f2f6f9", "actorBkg": "#eef1f4", "actorTextColor": "#161a1d", "actorLineColor": "#006ad4", "signalColor": "#006ad4", "signalTextColor": "#161a1d", "labelBoxBkgColor": "#ffffff", "labelTextColor": "#4f5356", "loopTextColor": "#4f5356", "noteBkgColor": "#f2f6f9", "noteTextColor": "#4f5356", "activationBkgColor": "#ded1e4", "activationBorderColor": "#9ea1a5"}}}%% sequenceDiagram participant A as App participant S as Spike API A->>+S: POST /auth/hmac
HMAC signature S-->>-A: Access Token A->>+S: POST /nutrition_records
Base64 image + token (wait_on_process: true) activate S S-->>A: 200 OK
Complete nutrition analysis with status: "completed" deactivate S S->>-A: POST webhook URL
Complete nutrition results activate A A-->>+S: 200 OK deactivate A ``` Waits for complete analysis before responding. Best for batch processing, server-to-server integrations, or when you can handle longer response times: **Request:** ```json theme={null} { "body": "base64-encoded-image-data", "analysis_mode": "fast", "wait_on_process": true } ``` **Response:** ```json theme={null} { "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: ```json theme={null} { "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 French omelette ```json Request Body theme={null} { "include_dish_description": true, "include_ingredients": true, "wait_on_process": true, "country_code": "fr", "language_code": "fr", "body_url": "https://images.ricardocuisine.com/services/recipes/992x1340_9168.jpg" } ``` ```json Response Body theme={null} { "record_id": "013501f8-da8f-88ed-8ef8-a21ad5d0e7bf", "status": "completed", "dish_name": "french omelette with mixed salad", "dish_description": "a classic French omelette made with whisked eggs, gently cooked for a soft texture, garnished with chives, served with a side of mixed green salad and a slice of toasted bread", "dish_name_translated": "Omelette française avec salade composée", "dish_description_translated": "Une omelette française classique, préparée avec des œufs battus, cuite doucement pour une texture moelleuse, garnie de ciboulette, servie avec une salade verte composée et une tranche de pain grillé.", "serving_size": 197, "unit": "g", "nutritional_fields": { "carbohydrate_g": 18.3, "energy_kcal": 288, "fat_total_g": 15.4, "protein_g": 17.3 }, "ingredients": [ { "name": "egg, whole, cooked, omelette", "name_translated": "œuf entier cuit en omelette", "serving_size": 120, "unit": "g", "nutritional_fields": { "carbohydrate_g": 2.4, "energy_kcal": 185, "fat_total_g": 14.1, "protein_g": 13.3 } }, { "name": "chives, raw", "name_translated": "ciboulette crue", "serving_size": 2, "unit": "g", "nutritional_fields": { "carbohydrate_g": 0.2, "energy_kcal": 1, "protein_g": 0.1 } }, { "name": "lettuce, mixed salad greens, raw", "name_translated": "laitue, salades composées, crues", "serving_size": 40, "unit": "g", "nutritional_fields": { "carbohydrate_g": 0.7, "energy_kcal": 7, "fat_total_g": 0.1, "protein_g": 0.8 } }, { "name": "bread, white, toasted", "name_translated": "pain blanc grillé", "serving_size": 35, "unit": "g", "nutritional_fields": { "carbohydrate_g": 15, "energy_kcal": 95, "fat_total_g": 1.2, "protein_g": 3.1 } } ], "uploaded_at": "2025-09-15T12:51:41.886Z", "modified_at": "2025-09-15T12:51:49.861Z", "consumed_at": "2025-09-15T12:51:41Z" } ``` ### Including Optional Data Control what data is included in the analysis: ```json theme={null} { "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: ```json theme={null} { "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 * **updated** — an existing record was updated with a new serving size or fully replaced REST API responses and webhook notifications will always include the `status` field. ```json theme={null} { "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`, `updated` or `failed`. 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 ```json theme={null} "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 ```json theme={null} "status": "failed", "dish_name": "unidentifiable", "failure_reason": "image too blurry to identify any food items clearly", ``` * **AI processing timeouts** — although AI providers typically have fast [Response Times](/nutrition-ai/overview#response-times), processing may occasionally take longer than expected due to reasons that cannot be foreseen. To manage this, the upload process will time out if synchronous processing takes longer than 3 minutes and asynchronous processing takes longer than 5 minutes. Once timeout occurs, the API will return HTTP status 200 and the body message with the `status` field set to `failed`: ```json theme={null} { "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:51:04.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. ```json theme={null} { "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. ```json theme={null} { "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. ```json theme={null} { "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. ```text theme={null} 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. ```json theme={null} { "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. ```json theme={null} { "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](/api-docs/errors). ### Nutrition Fields and Units The names of nutrition fields are always lowercase and include units as suffix. Nutrition fields are not translated. ## Custom Nutrition Records A full set of Nutrition AI endpoints allows sophisticated users to define their own nutrition records flexibly and precisely. Extracting nutrition details from nutrition facts labels lets users build up their own nutrition records from multiple ingredients. This is useful when users have existing nutritional data, follow recipes, or want to add custom items to the analyzed nutrition records. ### Creating Nutrition Records Manually [`POST /nutrition_records/manual`](/api-reference/nutrition-ai-upload-nutrition-record) endpoint allows you to define a full nutrition record with all ingredients and their respective nutritional fields: * Dish name and description including translation * Serving size and unit * Complete nutritional fields * Individual ingredients with their nutritional breakdown * Consumption timestamp ### Updating Nutrition Records Once a nutrition record exists (whether created manually or via AI analysis), you can update it in several ways: #### 1. Changing Serving Size Use [`PATCH /nutrition_records/{id}`](/api-reference/nutrition-ai-modify-nutrition-record) endpoint to adjust the portion size while maintaining the same nutritional ratios. All ingredients and their respective nutritional fields will be automatically recalculated proportionally. This allows you to easily adjust portion sizes while maintaining accurate nutritional information. #### 2. Updating the Consumption Time Use [`PATCH /nutrition_records/{id}`](/api-reference/nutrition-ai-modify-nutrition-record) endpoint to add or update the time of consumption. #### 3. Replacing Full Record Use [`PUT /nutrition_records/{id}`](/api-reference/nutrition-ai-replace-nutrition-record) to completely replace a record with new data, including different ingredients and nutritional values. A response will include an `input_type` field with value `manual` to indicate that the record was fully replaced. The record preserves the record ID only. The `consumed_at` and `user_time_offset_minutes` field values are updated either with newly provided values or set to defaults — the same as for a new record. ### Getting Results For asynchronous processing, use webhooks for real-time notifications. See the [Asynchronous Processing Guide](/nutrition-ai/async) for complete webhook implementation details. If webhooks are not yet available, you can check status using the [`GET /nutrition_records/{id}`](/api-reference/nutrition-ai-get-nutrition-record) endpoint. Retrospectively, a list of all analyzed nutrition records can be retrieved using the [`GET /nutrition_records`](/api-reference/nutrition-ai-list-nutrition-records) endpoint by providing the time range. ## Deleting Results A result can be deleted using [`DETELE /nutrition_records/{id}`](/api-reference/nutrition-ai-delete-nutrition-record) endpoint by providing the record ID. An HTTP 204 status code will be returned if successful, regardless if the record existed or not. ## 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: ```python [Python] theme={null} 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) ``` ```javascript [JavaScript] theme={null} const crypto = require('crypto'); const fs = require('fs'); const axios = require('axios'); class NutritionAPI { constructor(apiKey, baseUrl = 'https://api.spikeapi.com') { this.apiKey = apiKey; this.baseUrl = baseUrl; } createAuthHeaders() { // Create authentication headers - see /api-docs/authentication for details // Implementation depends on your authentication method return {}; } async uploadFoodImage(imagePath, options = {}) { // Read and encode image const imageBuffer = fs.readFileSync(imagePath); const imageBase64 = imageBuffer.toString('base64'); // Prepare the request body const body = { body: imageBase64, analysis_mode: options.analysisMode || 'precise', country_code: options.countryCode || 'us', language_code: options.languageCode || 'en', include_ingredients: options.includeIngredients !== false, include_nutri_score: options.includeNutriScore !== false, include_dish_description: options.includeDishDescription !== false, include_nutrition_fields: options.includeNutritionFields || [ 'energy_kcal', 'protein_g', 'fat_total_g', 'carbohydrate_g', 'fiber_total_dietary_g', 'sodium_mg' ], wait_on_process: options.waitOnProcess || false }; // Create request body and headers const bodyJson = JSON.stringify(body); const authHeaders = this.createAuthHeaders(); const headers = { 'Content-Type': 'application/json', ...authHeaders // See /api-docs/authentication for authentication details }; // Make request try { const response = await axios.post( `${this.baseUrl}/nutrition_records`, bodyJson, { headers } ); return response.data; } catch (error) { throw new Error(`API request failed: ${error.response?.data?.message || error.message}`); } } } // Example usage async function analyzeFood() { const api = new NutritionAPI('your-api-key'); try { // Asynchronous analysis (recommended) const result = await api.uploadFoodImage('path/to/food-image.jpg', { includeIngredients: true, includeNutriScore: true, countryCode: 'us' }); console.log(`Analysis started. Record ID: ${result.record_id}`); console.log(`Status: ${result.status}`); // Synchronous analysis (wait for results) const syncResult = await api.uploadFoodImage('path/to/food-image.jpg', { waitOnProcess: true, includeIngredients: true }); if (syncResult.status === 'completed') { const nutritionData = syncResult.result; console.log(`Dish: ${nutritionData.dish_name}`); console.log(`Nutri-Score: ${nutritionData.nutri_score}`); nutritionData.ingredients?.forEach(ingredient => { console.log(`- ${ingredient.name}: ${ingredient.serving_size}${ingredient.unit}`); }); } } catch (error) { console.error('Error:', error.message); } } analyzeFood(); ``` ```go [Go] theme={null} package main import ( "bytes" "crypto/hmac" "crypto/sha256" "encoding/base64" "encoding/hex" "encoding/json" "fmt" "io" "net/http" "os" "strconv" "time" ) type NutritionAPI struct { APIKey string BaseURL string } type UploadRequest struct { Body string `json:"body"` AnalysisMode string `json:"analysis_mode"` CountryCode string `json:"country_code"` LanguageCode string `json:"language_code"` IncludeIngredients bool `json:"include_ingredients"` IncludeNutriScore bool `json:"include_nutri_score"` IncludeDishDescription bool `json:"include_dish_description"` IncludeNutritionFields []string `json:"include_nutrition_fields"` WaitOnProcess bool `json:"wait_on_process"` } type UploadResponse struct { ApplicationID int64 `json:"application_id"` UID string `json:"uid"` RecordID string `json:"record_id"` Status string `json:"status"` Result *NutritionRecord `json:"result,omitempty"` Error string `json:"error,omitempty"` UploadedAt time.Time `json:"uploaded_at"` } type NutritionRecord struct { RecordID string `json:"record_id"` Status string `json:"status"` DishName string `json:"dish_name"` NutriScore string `json:"nutri_score"` ServingSize float64 `json:"serving_size"` Unit string `json:"unit"` NutritionalFields map[string]float64 `json:"nutritional_fields"` Ingredients []NutritionIngredient `json:"ingredients"` UploadedAt time.Time `json:"uploaded_at"` } type NutritionIngredient struct { Name string `json:"name"` ServingSize float64 `json:"serving_size"` Unit string `json:"unit"` NutritionalFields map[string]float64 `json:"nutritional_fields"` } func NewNutritionAPI(apiKey string) *NutritionAPI { return &NutritionAPI{ APIKey: apiKey, BaseURL: "https://api.spikeapi.com", } } func (api *NutritionAPI) createAuthHeaders() map[string]string { // Create authentication headers - see /api-docs/authentication for details // Implementation depends on your authentication method return map[string]string{ "Content-Type": "application/json", // Add authentication headers here } } func (api *NutritionAPI) UploadFoodImage(imagePath string, options UploadRequest) (*UploadResponse, error) { // Read and encode image imageData, err := os.ReadFile(imagePath) if err != nil { return nil, fmt.Errorf("failed to read image: %w", err) } options.Body = base64.StdEncoding.EncodeToString(imageData) // Set defaults if options.AnalysisMode == "" { options.AnalysisMode = "precise" } if options.CountryCode == "" { options.CountryCode = "us" } if options.LanguageCode == "" { options.LanguageCode = "en" } if options.IncludeNutritionFields == nil { options.IncludeNutritionFields = []string{ "energy_kcal", "protein_g", "fat_total_g", "carbohydrate_g", "fiber_total_dietary_g", "sodium_mg", } } // Create a request body bodyBytes, err := json.Marshal(options) if err != nil { return nil, fmt.Errorf("failed to marshal request: %w", err) } // Create request req, err := http.NewRequest("POST", api.BaseURL+"/nutrition_records", bytes.NewBuffer(bodyBytes)) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } // Set headers authHeaders := api.createAuthHeaders() for key, value := range authHeaders { req.Header.Set(key, value) } // See /api-docs/authentication for authentication details // Make request client := &http.Client{Timeout: 60 * time.Second} resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("request failed: %w", err) } defer resp.Body.Close() // Parse response respBody, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response: %w", err) } var result UploadResponse if err := json.Unmarshal(respBody, &result); err != nil { return nil, fmt.Errorf("failed to parse response: %w", err) } return &result, nil } func main() { api := NewNutritionAPI("your-api-key") // Asynchronous analysis result, err := api.UploadFoodImage("path/to/food-image.jpg", UploadRequest{ IncludeIngredients: true, IncludeNutriScore: true, CountryCode: "us", }) if err != nil { fmt.Printf("Error: %v\n", err) return } fmt.Printf("Analysis started. Record ID: %s\n", result.RecordID) fmt.Printf("Status: %s\n", result.Status) // Synchronous analysis syncResult, err := api.UploadFoodImage("path/to/food-image.jpg", UploadRequest{ WaitOnProcess: true, IncludeIngredients: true, }) if err != nil { fmt.Printf("Error: %v\n", err) return } if syncResult.Status == "completed" && syncResult.Result != nil { nutrition := syncResult.Result fmt.Printf("Dish: %s\n", nutrition.DishName) fmt.Printf("Nutri-Score: %s\n", nutrition.NutriScore) for _, ingredient := range nutrition.Ingredients { fmt.Printf("- %s: %.1f%s\n", ingredient.Name, ingredient.ServingSize, ingredient.Unit) } } } ``` For complete API specification, data types, and additional parameters, see the [`POST /nutrition_records`](/api-reference/nutrition-ai-analyze-nutrition-image) API Reference. # Nutrition AI Overview Source: https://docs.spikeapi.com/nutrition-ai/overview Overview of the Spike Nutrition AI service and how to use it ## About Upload food images and receive detailed nutritional analysis powered by AI. The Nutrition API uses advanced computer vision and machine learning to identify ingredients, calculate nutritional values, and provide comprehensive food composition data. ## Getting Started 1. Get an access token using the authentication flow (see [Authentication](/api-docs/authentication)) 2. Upload base64-encoded food images using the POST endpoint 3. Retrieve results either synchronously or via webhook notifications ## Key Features ### AI-Powered Analysis * Advanced computer vision for food identification * LANGUAL standard terminology for precise ingredient classification * Machine learning models for accurate nutritional calculations ### Flexible Processing * **Asynchronous processing** (recommended) — returns immediately with background processing * **Synchronous processing** — waits for complete analysis before returning results * **Webhook notifications** — for real-time status updates ### Comprehensive Data * **29 nutritional fields** from basic macros to detailed micronutrients * **Ingredient breakdown** with per-item nutritional analysis * **Nutri-Score rating** (A-E European scale) * **Localized results** with country and language support ## Technical Requirements ### Image Specifications * **Minimum size** — 512×512 pixels * **Maximum size** — 10MB when base64-encoded * **Supported formats** — JPEG, PNG, WebP * **Input format** — base64-encoded string ### Processing Workflow 1. **Image Upload & Validation** — system validates format compatibility 2. **AI Model Processing** — multiple AI models are used for analysis 3. **Ingredient Identification** — foods identified using LANGUAL terminology 4. **Nutritional Analysis** — detailed breakdown calculated per 100 g/ml 5. **Translation** (optional) — results translated to specified language 6. **Storage** — results stored with a configurable retention policy ## Analysis Report Data ### Main Data * Dish name with optional translation * Dish description with optional translation * Nutri-Score level (A-E) * Serving size with unit (g/ml) ### Basic Nutritional Fields * Energy (kcal) * Protein (g) * Total Fat (g) * Carbohydrates (g) ### Extended Nutritional Data * **Fats** — saturated, polyunsaturated, monounsaturated, trans fats * **Micronutrients** — fiber, sugars, cholesterol * **Minerals** — sodium, potassium, calcium, iron, magnesium, phosphorus, zinc * **Vitamins** — A, C, D, E, K, B-complex vitamins See the [Reference](/technical-references/nutritional_fields) for a complete list of nutritional fields. ## Image Guidelines For optimal analysis results, guide your users to: 1. **Center the food** — capture the contents of the plate as the main subject 2. **Fill the frame** — ensure the meal occupies the majority of the image 3. **Remove distractions** — take off packaging or utensils before photographing 4. **Clean background** — keep background clutter-free, include only the plate 5. **Use proper lighting** — natural or bright lighting to capture food details clearly 6. **Optimal angle** — slight angle to minimize items obscuring each other 7. **Avoid filters** — don't use filters that might alter the food's appearance ## Implementation For detailed implementation examples, request/response schemas, and code samples, see: * **[Implementation Guide](/nutrition-ai/implementation)** — complete code examples and integration patterns * **[Asynchronous Processing](/nutrition-ai/async)** — real-time notifications and webhook implementation * **[API Reference](/api-reference/nutrition-ai-analyze-nutrition-image)** — detailed endpoint specifications and data types ### Language and Country Options The API supports translation of results into multiple languages and region-specific analysis through country codes. By default, responses are in English (en) with the United States (US) as the default country. Providing a country code parameter improves analysis precision by considering regional ingredients, portion sizes, and food compositions specific to that location. Supported languages follow ISO 639-1 codes, while country codes adhere to ISO 3166-1 alpha-2 format. ## Response Times Response times typically range from 4 to 30 seconds depending on image complexity and the completeness of nutrition fields requested. Processing times may vary due to internal factors in AI analysis and nutritional information compilation. You may use the option for the preference of analysis speed over precision. # Introduction Source: https://docs.spikeapi.com/overview Get started with Spike API - your gateway to health and fitness data integration # Getting Started with Spike API Spike API provides a powerful platform for integrating health and fitness data from various providers like Garmin, Fitbit, and more. This guide will help you understand the key differences between using our API directly versus using our SDK. ## Spike API Overview The Spike API enables seamless integration with health and fitness data providers through: * Secure authentication using HMAC signatures * Comprehensive endpoints for data access * Provider integration management * User authentication and data management hmac key ## API vs SDK: Choosing Your Integration Path The API offers robust control and server-side processing, while the SDK simplifies integration with automatic handling of common tasks. ### Using the API Directly ```mermaid theme={null} sequenceDiagram autonumber %% Group client actors box rgb(220,240,255) Client Side participant ClientApp as Client
Application participant ClientServer as Client
Server end %% Group platform actors box rgb(255,231,247) Spike Platform participant SpikeSDK as Spike
SDK participant SpikeAPI as Spike
API end %% Group provider actors box rgb(255,255,220) External Data participant Provider as Data
Provider end %% Main flow starts Note right of ClientApp: User requests integration
with a data provider ClientApp->>+SpikeSDK: Request provider integration SpikeSDK->>+SpikeAPI: Initiate integration process deactivate SpikeSDK SpikeAPI->>Provider: Integrate with provider Note right of Provider: Integration established
Provider syncs data Provider-->>SpikeAPI: Continuously sync data rect rgb(240,255,240) SpikeAPI->>ClientServer: Send webhook event (new data) ClientServer->>+SpikeAPI: Query new data details SpikeAPI-->>-ClientServer: Return integrated data end Note over SpikeAPI,Provider: Ongoing data synchronization deactivate SpikeAPI ```

Ideal for scenarios where:

  • Data needs to be available for central processing on a server
  • Developers require fine-grained control over data handling
  • Custom server-side authentication flows are necessary
For detailed API documentation, visit our [API Documentation](/api-docs/overview) ### Using the SDK

SDK Integration

```mermaid theme={null} sequenceDiagram participant ClientApp as Client Application participant SpikeSDK as Spike SDK participant SpikeAPI as Spike API participant Provider as Data Provider ClientApp->>SpikeSDK: Request to Integrate with Provider activate SpikeSDK SpikeSDK->>SpikeAPI: Start Provider Integration Process deactivate SpikeSDK SpikeAPI->>Provider: Establish Integration Provider->>SpikeAPI: Continuously Synchronize Data ClientApp->>SpikeSDK: Request Data Retrieval activate SpikeSDK SpikeSDK->>SpikeAPI: Forward Data Query deactivate SpikeSDK SpikeAPI->>ClientApp: Deliver Retrieved Data ```

Best suited for applications where:

  • Data is primarily used within the context of an app
  • Simplified integration with minimal setup is preferred
  • Automatic handling of authentication and error management is beneficial
For detailed SDK documentation, visit our [SDK Documentation](/sdk-docs/overview) # Android SDK Backfill Source: https://docs.spikeapi.com/sdk-docs/android/backfill Backfill historical data from Google Health Connect. ## Backfill from Google Health Connect Data is stored exclusively on the device (running Android). Backfilling is possible from the moment the application user grants permissions and is limited by: * What data the user has on their device * What permissions your app has been granted (read access for specific data types) * User's health app settings or deletions * Your retention policy settings * 30-day period predating permission grant ## Permission Timing and Historical Data Access Permission request timing is not relevant, meaning that if the application user decides to approve permission a week later by going directly into a Health Connect settings menu, you will be able to access data 23 days before installing the app. For apps targeting Android API level 34 (Android 14) and higher, Google has introduced [PERMISSION\_READ\_HEALTH\_DATA\_HISTORY](https://developer.android.com/health-and-fitness/guides/health-connect/develop/read-data#read-older-data), which allows access to health data recorded before the app was installed, extending beyond the standard 30-day limitation. ## Manual Data Extraction Required Because Google doesn't offer any other communication except a Health Connect client, you **must query needed data manually** to backfill it. We recommend keeping this process asynchronous for the best user experience and segmented to ensure the best performance. Before data is extracted, it won't be available to your backend over API calls. Enabling background data delivery also won't perform the backfill as it's designed for new data events. Ensure your app handles data synchronization efficiently to minimize battery and resource usage. Implement background processing where appropriate to avoid impacting app performance. ## Implementation ```kotlin theme={null} import java.time.LocalDate import java.time.ZoneId import java.time.Instant // Example: Backfill last 7 days of steps data val today = LocalDate.now() for (dayOffset in 0 until 7) { val startDate = today.minusDays(dayOffset.toLong()) val from: Instant = startDate.atStartOfDay(ZoneId.systemDefault()).toInstant() val to: Instant = startDate.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant() try { // Call the getStatistics method for each day val statistics = spikeConnection.getStatistics( types = setOf(StatisticsType.STEPS), from = from, to = to, interval = StatisticsInterval.DAY, filter = StatisticsFilter(providers = listOf(Provider.HEALTH_CONNECT)) ) // Process the statistics data statistics.forEach { statistic -> println("Steps for ${statistic.start}: ${statistic.value}") } } catch (e: SecurityException) { // Handle permission-related errors println("Permission denied. Guide user to Health Connect settings.") } catch (e: IllegalArgumentException) { // Handle invalid parameters println("Invalid request parameters: ${e.message}") } catch (e: Exception) { // Handle other errors gracefully println("Error fetching statistics for $startDate: ${e.localizedMessage}") } } ``` ## Best Practices ### Performance and User Experience * Keep the backfill process asynchronous for the best user experience * Segment requests into smaller date ranges to ensure optimal performance * Implement efficient data synchronization to minimize battery usage * Consider implementing a progress indicator for longer backfill operations * Use background processing where appropriate to avoid blocking the UI ### Privacy and Security * Handle permissions gracefully — users may grant partial access * Consider allowing users to control the backfill scope (date range, data types) * Comply with relevant regulations such as GDPR for user data protection ### Permission Management * Ensure your app handles the 30-day limitation appropriately * Consider implementing `PERMISSION_READ_HEALTH_DATA_HISTORY` for Android 14+ devices * Guide users to Health Connect settings when permissions are denied * Regularly check permission status and handle revoked permissions gracefully ### Error Handling and Data Management * Implement robust error handling for various exception types * Gracefully handle scenarios where data is unavailable or incomplete * Manage data conflicts and duplicates effectively to maintain data integrity * Store health data securely using Android's encryption and security best practices * Validate data before processing to ensure accuracy and consistency # Android SDK Background Delivery Source: https://docs.spikeapi.com/sdk-docs/android/background-delivery Background delivery ensures that data updates are sent to your backend via webhooks, even when the application is in the background or closed. ### Important Notes About Background Delivery on Android * Background delivery is scheduled to run every hour, but ultimately, Android decides when the delivery will be executed. * Android may throttle the frequency of updates for background delivery depending on the app's activity, battery state, etc. * Android may stop background delivery if it detects that the app is not active for a long time. * There is a limit of queries that can be done in Health Connect, and it is different for foreground and background reads, so please request only essential data to be delivered in the background. More information in Health Connect documentation. **Important**: The Spike SDK, along with any other applications, cannot guarantee data synchronization on a fixed schedule. The hourly sync interval serves as a guideline rather than a strict requirement enforced by Android. Consequently, the actual synchronization frequency may vary, occurring hourly, once per day, or during specific system-defined events, such as the conclusion of Sleep Mode or when the device begins charging. ## Setup Add the following permission to your \`AndroidManifest.xml: ```kotlin theme={null} ``` Check if background delivery is possible with the current Health Connect version: ```kotlin theme={null} val hcAvailability = spikeConnection.getHealthConnectPermissionManager() .isFeatureAvailable(HealthConnectFeature.READ_HEALTH_DATA_IN_BACKGROUND) ``` If not, you can ask the user to update Health Connect to the latest version. Ask for background read permission: ```kotlin theme={null} val permissionManager = spikeConnection.getHealthConnectPermissionManager() val requiredPermissions = permissionManager.getPermissions( // You can add other permissions as well: statistics, sleep, etc. includeBackgroundDelivery = true ) val permissionLauncher = rememberLauncherForActivityResult( permissionManager.getRequestPermissionResultContract(), onResult = { ... } ) permissionLauncher.launch(requiredPermissions) ``` You can also ask for background delivery permission at the same time as other permissions. Now you can enable background delivery: ```kotlin theme={null} connection.enableBackgroundDelivery( BackgroundDeliveryConfig( statisticsTypes = setOf(StatisticsType.STEPS) ) ) ``` Keep in mind that calling `enableBackgroundDelivery` will overwrite the previous configuration. If you want to add more types, you have to call `enableBackgroundDelivery` again with updated configuration: ```kotlin theme={null} connection.enableBackgroundDelivery( BackgroundDeliveryConfig( statisticsTypes = setOf(StatisticsType.STEPS), activityTypes = setOf(ActivityType.RUNNING, ActivityType.WALKING) ) ) ``` To check the current configuration, call the `getBackgroundDeliveryConfig()` method. To stop background delivery, call the `disableBackgroundDelivery()` method. ## Samsung Health If Samsung Health Data is enabled before enabling background delivery, `enableBackgroundDelivery` will automatically enable Samsung Health Data background delivery. # Android SDK Changelog Source: https://docs.spikeapi.com/sdk-docs/android/changelog A changelog documenting version updates and changes of Spike SDK for Android platform. ## 4.7.12 * Removed the deprecated v1/v2 connection API ## 4.6.12 * Added Health Connect and Samsung Health Data nutrition support. You can now request nutrition permissions, backfill historical nutrition data, and include nutrition fields in background delivery by passing `nutritionalFields`. ``` val nutritionalFields = setOf( NutritionalField.ENERGY_KCAL, NutritionalField.PROTEIN_G, NutritionalField.CARBOHYDRATE_G, NutritionalField.FAT_TOTAL_G ) val permissions = spikeConnection.getHealthConnectPermissionManager().getPermissions( nutritionalFields = nutritionalFields ) spikeConnection.backfill( nutritionalFields = nutritionalFields, days = 7 ) ``` * `getNutritionRecords(from, to)` now reads local Health Connect and Samsung Health Data nutrition data before returning nutrition records when those integrations are enabled. * Nutrition uploads now include magnesium values when provided by Health Connect. ## 4.5.32 * Added Health Connect and Samsung Health Data nutrition support. Pass a `Set` to the Health Connect or Samsung Health Data permission manager to include the nutrition read permission in the request, and the same set is also accepted by `enableBackgroundDelivery`, `backfill`, and `BackgroundDeliveryConfig` so nutrition is uploaded alongside your other data. Once the nutrition permission is granted, `getNutritionRecords(from, to)` automatically pushes the latest local nutrition data before returning analyzed results. ``` val permissions = spikeConnection.getHealthConnectPermissionManager().getPermissions( statisticsTypes = setOf(StatisticsType.STEPS), nutritionalFields = setOf( NutritionalField.ENERGY_KCAL, NutritionalField.PROTEIN_G, NutritionalField.CARBOHYDRATE_G, NutritionalField.FAT_TOTAL_G ) ) // ... after the user grants `permissions` via the Health Connect request contract ... val records = spikeConnection.getNutritionRecords(from = startDate, to = endDate) ``` For Samsung Health Data, pass the same `nutritionalFields` set to `getSamsungHealthDataPermissionManager().getPermissions(...)`, `requestPermissions(...)`, or `requestPermissionsFromSamsungHealthDataAndBackfill(...)`. * Updated Samsung Health Data to 1.1.0 ## 4.5.22 * Added `backfill` on the v3 connection to upload historical health data for a chosen number of days. Call it after you have enabled Health Connect and/or Samsung Health Data integration (`enableHealthConnectIntegration()`, `enableSamsungHealthDataIntegration(...)`) and granted the permissions you need via the respective permission managers; then pass the statistics, metrics, activities, and sleep configurations you want to fill in. Only the categories you provide are pushed. ``` spikeConnection.backfill( statisticsTypes = setOf(StatisticsType.STEPS), metricTypes = null, activityConfigs = null, sleepConfigs = null ) ``` * Added `requestPermissionsFromSamsungHealthDataAndBackfill` — a convenience method that enables Samsung Health Data integration, requests permissions, and immediately starts backfilling historical data. Today and yesterday are pushed before the method returns so you can display data right away; older days continue uploading in the background. ``` spikeConnection.requestPermissionsFromSamsungHealthDataAndBackfill( activity = this, statisticsTypes = setOf(StatisticsType.STEPS), metricTypes = null, activityConfigs = null, sleepConfigs = null, backfillDays = 7 ) ``` * Permission `PERMISSION_READ_HEALTH_DATA_HISTORY` is now added automatically to the list if required according to configuration in admin console. ## 4.5.12 * Improved handling of deleted integrations — background delivery is now automatically disabled when the integration no longer exists * Improved error handling for API responses * Added `RecordConfig` for configuring records queries: * `includeSamples` - whether to include samples (raw data points) in the response * Updated `getRecords` to accept an optional `RecordConfig` parameter: ``` spikeConnection.getRecords( types = metricTypes, from = startDate, to = endDate, filter = StatisticsFilter(excludeManual = true), config = RecordConfig(includeSamples = true) ) ``` ## 4.4.12 * Updated `Unit` enum with new values: * Added: `miles`, `mPerSec`, `fahrenheit`, `ms`, `seconds`, `degrees`, `sleepStage`, `secPerM`, `rpm`, `spm`, `breathsPerMin`, `g`, `lbs`, `st`, `mmHg`, `mLPerKgPerMin`, `uV`, `mgPerDl`, `w` * Removed: `kmh`, `kmPerMin` * Improved data communication with API * Enhanced the `getSleep` endpoint. Important: * Dates provided are now interpreted as dates only; the time component is ignored when querying the API. * The primary night sleep period is associated with the calendar date on which it ends. ## 4.3.142 * Internal improvements and bug fixes. ## 4.3.132 * Samsung Health Data Integration - Major Expansion: * Added support for reading and pushing the following data types from Samsung Health: * Workouts (exercises) * Sleep data * Body composition data (weight, height, BMI, body fat) * User profile data (weight, height, birth date, gender) * Blood pressure measurements * Skin temperature measurements * Heart rate measurements * Blood oxygen saturation (SpO2) measurements * Body temperature measurements * More efficient Samsung Health Data pushes * Large Samsung Health Data pushes are now automatically split into smaller chunks for reliable delivery * Health Connect Improvements: * Large Health Connect pushes are now automatically split into smaller chunks for reliable delivery ## 4.3.122 * Added Nutrition Facts Label Recognition for extracting nutritional information from label photos. For more information please visit our [documentation](https://docs.spikeapi.com). * `recognizeNutritionFactsLabel(imageBase64, config)` - Analyze a nutrition facts label image and extract nutritional data * `recognizeNutritionFactsLabel(image: Bitmap, config)` - Convenience method that accepts Bitmap * Added methods for manual nutrition record management: * `createNutritionRecord(nutritionRecord)` - Insert a new nutrition record with custom data * `replaceNutritionRecord(nutritionRecord)` - Replace an existing nutrition record completely * Added `NutritionRecordUtils` for creating and manipulating nutrition records: * `NutritionRecordUtils.create(...)` - Create a new nutrition record with specified properties * `NutritionRecord.withIngredient(ingredient)` - Add an ingredient to a record * `NutritionRecord.withoutIngredient(ingredient)` - Remove an ingredient from a record * `NutritionRecord.withServingSize(servingSize, unit)` - Update serving size and unit * Added `NutritionRecordIngredientUtils` for creating and manipulating ingredients: * `NutritionRecordIngredientUtils.create(...)` - Create a new nutrition record ingredient * `NutritionRecordIngredient.withNutritionalField(field, value)` - Set a nutritional field value * `NutritionRecordIngredient.withoutNutritionalField(field)` - Remove a nutritional field * `NutritionRecordIngredient.withScaledServingSize(newServingSize)` - Scale serving size and nutritional values proportionally * Nutrition AI methods now return `NutritionRecordAnalysisResult` instead of `NutritionRecord`. The new `NutritionRecordAnalysisResult` contains: * `recordId` - The record ID * `status` - Processing status (pending, processing, completed, failed, updated) * `failureReason` - Reason for failure (if status is failed) * `uploadedAt` - Upload timestamp * `nutritionRecord` - The actual nutrition data (nullable, only present when status is completed/updated) **Affected methods:** * `analyzeNutrition(...)` - Now returns `NutritionRecordAnalysisResult` * `getNutritionRecords(from, to)` - Now returns `List` * `getNutritionRecord(id)` - Now returns `NutritionRecordAnalysisResult?` * `updateNutritionRecordServingSize(id, servingSize)` - Now returns `NutritionRecordAnalysisResult` * `createNutritionRecord(nutritionRecord)` - Now returns `NutritionRecordAnalysisResult` * `replaceNutritionRecord(nutritionRecord)` - Now returns `NutritionRecordAnalysisResult` Access the nutrition data via the `nutritionRecord` property: ```kotlin theme={null} val result = spikeConnection.analyzeNutrition(image) val dishName = result.nutritionRecord?.dishName // Previously: result.dishName val status = result.status // Status is now on the result object ``` * `NutritionRecord` no longer contains `status` and `failureReason` properties. These properties have been moved to `NutritionRecordAnalysisResult`. ## 4.3.112 * Added Nutrition AI features for analyzing food images. For more information please visit our [documentation](https://docs.spikeapi.com). * `analyzeNutrition(imageBase64, consumedAt, config)` - Analyze food image synchronously and wait for results * `analyzeNutrition(image: Bitmap, consumedAt, config)` - Convenience method that accepts Bitmap * `submitNutritionForAnalysis(imageBase64, consumedAt, config)` - Analyze food image asynchronously, returns record ID * `submitNutritionForAnalysis(image: Bitmap, consumedAt, config)` - Convenience method that accepts Bitmap * `getNutritionRecords(from, to)` - Retrieve nutrition records for a date range * `getNutritionRecord(id)` - Get a specific nutrition record by ID * `deleteNutritionRecord(id)` - Delete a nutrition record * `updateNutritionRecordServingSize(id, servingSize)` - Update serving size for a nutrition record * Added new `Provider`: * LUNA * Added new `ProviderSource` types: * FITBIT\_SKIN\_TEMPERATURE\_SUMMARY * GARMIN\_WELLNESS\_SKIN\_TEMPERATURE\_SUMMARY * LUNA\_SLEEP * Removed `ProviderSource` types: * FITBIT\_USER\_ACTIVITIES\_LIST * FITBIT\_USER\_ACTIVITIES\_DATE * FITBIT\_USER\_ACTIVITIES\_HEART\_DATE * FITBIT\_USER\_ACTIVITIES\_HEART\_DATE\_INTERDAY * WITHINGS\_MEASURE\_GET\_WORKOUTS * WITHINGS\_MEASURE\_GET\_ACTIVITY * WITHINGS\_MEASURE\_GET\_MEAS * Removed `ActivityTag`: * ON\_BICYCLE * Removed `Provider`: * WAHOO ## 4.3.102 * Enable Health Connect integration in admin console right after enabling integration * Enable Samsung Health Data integration in admin console right after enabling integration * 🔴 **Breaking Change**: `enableHealthConnectIntegration()` is now a suspend function. Update your code to call it from a coroutine scope. * 🔴 **Breaking Change**: `enableSamsungHealthDataIntegration()` is now a suspend function. Update your code to call it from a coroutine scope. ## 4.3.92 * Updated Health connect to `1.1.0-rc03` * Updated compileSdk to `36` * Updated gradle to `8.13` * Removed `providerUserIdentifier` from `IntegrationInitConfig` * Added ability to read mindfulness activities: ``` if (spikeConnection.getHealthConnectPermissionManager() .isFeatureAvailable(feature: HealthConnectFeature.FEATURE_MINDFULNESS_SESSION)) { spikeConnection.getActivities( config: ActivityConfig(activityCategories: [ActivityCategory.MINDFULNESS]) from: dateFrom, to: dateTo ) } ``` * Added new `ActivityTag` types: * MINDFULNESS * Added new `ActivityType` types: * ECG\_MEASUREMENT * TRIATHLON * BIATHLON * DUATHLON * ROLLERBLADING * SKATEBOARDING * SKATING * CALISTHENICS * WEIGHT\_LIFTING * CANOEING * FLOORBALL * JIU\_JITSU * DIVING * ORIENTEERING * BOOTCAMP * MOTORSPORTS * HORSERIDING * PARAGLIDING * MULTISPORT * BLOOD\_TEST * MINDFULNESS\_SESSION * Added new `ProviderSource` types: * APPLE\_HEALTHKIT\_MINDFULNESS * HEALTH\_CONNECT\_MINDFULNESS\_SESSION * Added new `ActivityCategory` type: * MINDFULNESS * Added new `HealthConnectFeature`: * FEATURE\_MINDFULNESS\_SESSION * Added new `MetricType`: * HEARTRATE\_RESTING\_MIN * HEARTRATE\_RESTING\_MAX * CADENCE * CADENCE\_MIN * CADENCE\_MAX * PACE * AIR\_TEMPERATURE * BODY\_TEMPERATURE\_MAX * BODY\_TEMPERATURE\_MIN * BASAL\_BODY\_TEMPERATURE * BASAL\_BODY\_TEMPERATURE\_MAX * BASAL\_BODY\_TEMPERATURE\_MIN * SKIN\_TEMPERATURE\_MAX * SKIN\_TEMPERATURE\_MIN * SLEEP\_SKIN\_TEMPERATURE\_DEVIATION * Added new `StatisticsType`: * SLEEP\_SKIN\_TEMPERATURE\_DEVIATION * HRV\_RMSSD * HRV\_SDNN ## 4.3.82 * Workaround for the Android 15 bug to make background delivery more resilient * Implemented more efficient pushes to Spike API ## 4.3.72 * Allow reading `SPO2` and `SWIMMING_LENGTHS` in `getActivities` * Allow reading `SPO2` and `SKIN_TEMPERATURE` in `getSleeps` ## 4.3.62 * Optimized background delivery ## 4.3.52 * Added `Provider.COROS` * Updated `ProviderSource` enum * Added new Statistic types: * STRESS\_SCORE * RECOVERY\_SCORE * ACTIVITY\_SCORE * Added new Metric types for `getSleep` request: * SLEEP\_DURATION * SLEEP\_DURATION\_AWAKE * SLEEP\_DURATION\_DEEP * SLEEP\_DURATION\_LIGHT * SLEEP\_DURATION\_NAP * SLEEP\_DURATION\_REM * SLEEP\_EFFICIENCY * SLEEP\_INTERRUPTIONS * SLEEP\_LATENCY * SLEEP\_SCORE ## 4.3.42 * Added new `MetricType.GLUCOSE` available in both Health Connect and Samsung Health * Updated Samsung Health Data SDK to version 1.0.0 (available only on Android 29+) ## 4.3.32 * Switched to protobuf java-lite 3.25.5 for better compatibility with firebase * The old SpikeSDK API has been deprecated ## 4.3.22 * Added DISTANCE\_SWIMMING into the list of metrics available in activities * Added new providers: DEXCOM, FREESTYLE\_LIBRE, HUAWEI, STRAVA ## 4.3.12 * Added new statistics for Health Connect: * HEARTRATE * HEARTRATE\_MAX * HEARTRATE\_MIN * Added new metrics for Health Connect: * HEARTRATE * HEARTRATE\_MAX * HEARTRATE\_MIN * Improved transport protocol for even faster requests to Spike API * Added Samsung Health Integration for: * `StatisticType`s: * STEPS * DISTANCE\_TOTAL * CALORIES\_BURNED\_ACTIVE * CALORIES\_BURNED\_TOTAL * CALORIES\_BURNED\_BASAL * Added new `Provider`: * SAMSUNG\_HEALTH\_DATA * Added new `ProviderSource`: * SAMSUNG\_HEALTH\_DATA\_AGGREGATION * Renamed `MetricType`s: * SLEEP\_BREATHING\_RATE to BREATHING\_RATE * SLEEP\_BREATHING\_RATE\_MIN to BREATHING\_RATE\_MIN * SLEEP\_BREATHING\_RATE\_MAX to BREATHING\_RATE\_MAX ## 4.2.82 * Added `IntegrationInitConfigUtils` for usage in Flutter and React Native libraries * Additional check in `getIntegrationInitUrl` for email in ultrahuman integration ## 4.2.72 * Added new `MetricType`s: * BODY\_FAT * BODY\_FAT\_MAX * BODY\_FAT\_MIN * BODY\_BONE\_MASS * BODY\_MASS\_INDEX * BLOOD\_PRESSURE\_SYSTOLIC * BLOOD\_PRESSURE\_SYSTOLIC\_MIN * BLOOD\_PRESSURE\_SYSTOLIC\_MAX * BLOOD\_PRESSURE\_DIASTOLIC * BLOOD\_PRESSURE\_DIASTOLIC\_MIN * BLOOD\_PRESSURE\_DIASTOLIC\_MAX * Added new fields in `UserProperties`: * BODY\_FAT * BODY\_BONE\_MASS * BODY\_MASS\_INDEX * Updated `getIntegrationInitUrl`. Now it accepts `IntegrationInitConfig` config object where you can pass: * `redirectUri`: will override the one set in admin console * `state`: when the authorization server redirects back to the client, it includes the `state` value originally sent * `providerUserIdentifier`: at the moment used (and required) only when integrating with ultrahuman * Added new provider: `ultrahuman` When integrating with Ultrahuman, you have to provide Ultrahuman user email in `IntegrationInitConfig.providerUserIdentifier`. Example: ``` spikeConnection.getIntegrationInitUrl(provider = Provider.ULTRAHUMAN, config = IntegrationInitConfig(providerUserIdentifier = "user@mail.com")) ``` ## 4.2.62 * Added consumer proguard rules ## 4.2.52 * New statistics: * HEARTRATE\_RESTING * SLEEP\_DURATION\_TOTAL * New statistics (only from non-HealthConnect providers): * HEARTRATE * HEARTRATE\_MIN * HEARTRATE\_MAX * Metric types updates * Better proguard settings for uniqueness of generated class names * Fix for statistics in different time zones ## 4.2.42 * New metrics: * SPO2 * BODY\_TEMPERATURE * SKIN\_TEMPERATURE * SLEEP\_BREATHING\_RATE (available only on getSleep()) * SLEEP\_BREATHING\_RATE\_MIN (available only on getSleep()) * SLEEP\_BREATHING\_RATE\_MAX (available only on getSleep()) * SLEEP\_BREATHING\_RATE\_AVG (available only on getSleep()) * Added sleepScore property in the Record type ## 4.2.32 * New `getUserProperties` method for reading: weight, height, timezone from Health Connect * New `getUserProperties` method for reading: birthdate, gender from other providers * New metric: `VO2MAX` available in `getRecords` and `getActivities` * New statistic: `SLEEP_SCORE` ## 4.2.22 * Implemented better API error handling * Exposed `SpikeHMACSignatureGenerator` for debugging purposes ## 4.2.12 * 🔴 **Breaking Change**: Update to `SpikeConnectionAPIv3.createConnection`. The method `SpikeConnectionAPIv3.createConnection(context:, appId:, authToken:, customerEndUserId:)` has been updated to: `SpikeConnectionAPIv3.createConnection(context:, applicationId:, signature:, endUserId:)` * The `applicationId` parameter must now be provided as an **`Int`**. * The `signature` parameter now requires an **`HMAC-SHA256` signed user ID**. * ⚠️ **Security Notice:** * **Do not store your HMAC signing key within the application itself**, as this poses a security risk. * Instead, generate and provide the signature from your backend. * **Legacy Support**: For **development purposes only**, the previous connection flow remains available under the renamed method: `SpikeConnectionAPIv3.createConnectionLegacy(context:, appId:, authToken:, customerEndUserId:)` ## 4.1.12 * Added background delivery (see documentation for more information) * Health Connect library updated to 1.1.0-alpha11 * Compile SDK and target SDK updated to 35 * Updated kotlin version to 1.9.25 * Updated other dependencies * Added log callback * Updated gradle to 8.8.0 ## 4.0.22 Renamed `getProviderIntegrationUrl` to `getIntegrationInitUrl` to be on par with other platforms ## 4.0.12 Completely new SDK! Please see our official documentation for more details and usage instructions ## 3.1.6 * Updated steps intraday to better check for manual entries * Added permissions specific to steps\_intraday ## 3.1.5 * Implemented checking for permissions before reading additional sleep data ## 3.1.4 * Added metadata in Calories raw data ## 3.1.3 * Reverted 3.1.2 changes ## 3.1.2 * Added third party integration initialization ## 3.1.1 * Removed unneeded settings from android manifest ## 3.1.0 * Better way to authenticate with API * Added saved session validation before the connection is established * Added customer user id validation. ## 3.0.19 * Added safeguard for to handle multiple sources of sleep data and avoid illogical values ## 3.0.18 * Added new logic for detecting changes in intraday\_steps metadata ## 3.0.17 * Removed the limitation that prevented values from being sent when the requested period is more than a day * Implemented calculation of time by sleep stage in sleep data ## 3.0.16 * Updated metadata info in intraday\_steps to have all values as single objects * Refactored how the decision whether to send metadata with intraday\_steps is made ## 3.0.15 * Added all metadata info to each intraday\_steps data object ## 3.0.14 * Fixed sleep time counting by different stages ## 3.0.13 * Added a source field to the sleep data class ## 3.0.11 * Fixed data type StepsIntraday handling ## 3.0.9 * Added a new Spike data type — `IntradaySteps` ## 3.0.8 * Updated the package checker function to avoid unnecessary checks for Android 14 and up versions. ## 3.0.7 * Can get permission contract before SpikeConnection is created ## 3.0.6 * Moved permission checking before data extraction to the SDK connection layer ## 3.0.5 * Fixed extraction of heart data ## 3.0.4 * Added support for Android 14 * Updated Readme to match the Android 14 implementation process * Fixed some issues with data requesting for a specified date range ## 3.0.2: * Activity summary and activity stream can be called without granting all their permissions. Returned data depends on which permissions were provided. * Permission requests are divided by datatypes. * Works up to Android 13. # Android SDK Logging Source: https://docs.spikeapi.com/sdk-docs/android/logging Getting logs for troubleshooting and debugging from Spike SDK for Android platform. ## Code Examples By default, SPike SDK logs are sent to standard Android Logcat. If for some reason you want to read them, you can use the following code (running it before you start using SpikeSDK): ```kotlin theme={null} SpikeConnectionAPIv3.setLogCallback { level, message -> when (level) { LogLevel.VERBOSE -> {} LogLevel.DEBUG -> if(BuildConfig.DEBUG) { Log.d("SpikeSDK", message) } LogLevel.WARNING -> Log.w("SpikeSDK", message) LogLevel.ERROR -> Log.e("SpikeSDK", message) } } ``` If you use Background Delivery, this code should be run in your application's onCreate() method: ```kotlin theme={null} class MyApplication: Application() { override fun onCreate() { super.onCreate() SpikeConnectionAPIv3.setLogCallback { level, message -> when (level) { LogLevel.VERBOSE -> {} LogLevel.DEBUG -> if(BuildConfig.DEBUG) { Log.d("SpikeSDK", message) } LogLevel.WARNING -> Log.w("SpikeSDK", message) LogLevel.ERROR -> Log.e("SpikeSDK", message) } } } } ``` Also, the application should be set in AndroidManifest.xml: ```kotlin theme={null} ``` # Android SDK Nutrition AI Source: https://docs.spikeapi.com/sdk-docs/android/nutrition-ai Analyze food images and retrieve nutritional information using AI-powered analysis in your Android 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 Android application. The SDK handles image encoding, API communication, and response parsing, making it easy to integrate nutritional analysis into your app. All Spike SDK suspending methods should be called from a coroutine scope and wrapped in try-catch blocks. See [Error Handling](#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 * **`Bitmap` Support** — convenient methods that accept `Bitmap` 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. ```kotlin theme={null} import com.spikeapi.apiv3.SpikeConnectionAPIv3 import com.spikeapi.apiv3.datamodels.* import java.time.Instant // image: Bitmap - captured from camera or photo library val image: Bitmap = // ... captured from camera or gallery try { val record = spikeConnection.analyzeNutrition( image = image, consumedAt = Instant.now(), config = NutritionalAnalysisConfig( analysisMode = NutritionRecordAnalysisMode.PRECISE, countryCode = null, languageCode = null, includeNutriScore = true, includeDishDescription = true, includeIngredients = true, includeNutritionFields = listOf( NutritionalField.ENERGY_KCAL, NutritionalField.PROTEIN_G, NutritionalField.FAT_TOTAL_G, NutritionalField.CARBOHYDRATE_G ) ) ) println("Dish: ${record.dishName ?: "Unknown"}") println("Serving size: ${record.servingSize ?: 0} ${record.unit?.value ?: "g"}") println("Calories: ${record.nutritionalFields?.get("energy_kcal") ?: 0}") } catch (e: Exception) { println("Analysis failed: ${e.message}") } ``` You can also use base64-encoded image data: ```kotlin theme={null} // Using base64-encoded string val outputStream = ByteArrayOutputStream() image.compress(Bitmap.CompressFormat.JPEG, 80, outputStream) val base64String = Base64.encodeToString(outputStream.toByteArray(), Base64.NO_WRAP) val record = spikeConnection.analyzeNutrition( imageBase64 = base64String, consumedAt = Instant.now(), config = null // Uses default configuration ) ``` **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. ```kotlin theme={null} try { // Submit image for background processing val recordId: UUID = spikeConnection.submitNutritionForAnalysis( image = image, consumedAt = Instant.now(), config = NutritionalAnalysisConfig( analysisMode = NutritionRecordAnalysisMode.FAST, countryCode = null, languageCode = null, includeNutriScore = null, includeDishDescription = null, includeIngredients = true, includeNutritionFields = null ) ) println("Analysis started. Record ID: $recordId") // Optionally, poll for results later // Your backend will also receive a webhook when analysis completes } catch (e: Exception) { println("Failed to submit: ${e.message}") } ``` #### 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. ```kotlin theme={null} // Check the status and get results val record = spikeConnection.getNutritionRecord(id = recordId) if (record != null) { when (record.status) { NutritionRecordStatus.COMPLETED -> { println("Analysis complete: ${record.dishName ?: "Unknown"}") } NutritionRecordStatus.PROCESSING -> { println("Still processing...") } NutritionRecordStatus.PENDING -> { println("Queued for processing...") } NutritionRecordStatus.FAILED -> { println("Analysis failed: ${record.failureReason ?: "Unknown error"}") } NutritionRecordStatus.UNKNOWN -> { println("Unknown status") } } } ``` 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. ## Configuration Options Customize the analysis using `NutritionalAnalysisConfig`: ```kotlin theme={null} val config = NutritionalAnalysisConfig( // 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 (using NutritionalField enum) includeNutritionFields = listOf( NutritionalField.ENERGY_KCAL, NutritionalField.PROTEIN_G, NutritionalField.FAT_TOTAL_G, NutritionalField.CARBOHYDRATE_G, NutritionalField.FIBER_TOTAL_DIETARY_G, NutritionalField.SODIUM_MG ) ) ``` ### `NutritionalAnalysisConfig` ```kotlin theme={null} data class NutritionalAnalysisConfig( /** A preferred mode for the analysis. Default is PRECISE. */ val analysisMode: NutritionRecordAnalysisMode?, /** Country ISO 3166-1 alpha-2 code in lowercase */ val countryCode: String?, /** Language ISO 639-1 code in lowercase */ val languageCode: String?, /** Include nutri-score label of the food. Default is false. */ val includeNutriScore: Boolean?, /** Include dish description of the food. Default is false. */ val includeDishDescription: Boolean?, /** Include ingredients of the food. Default is false. */ val includeIngredients: Boolean?, /** * Include specific nutrition fields in the analysis report. * By default, carbohydrate_g, energy_kcal, fat_total_g and protein_g will be included. */ val includeNutritionFields: List? ) ``` ### Analysis Modes ```kotlin theme={null} enum class NutritionRecordAnalysisMode(val value: String) { 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: * `ENERGY_KCAL` * `PROTEIN_G` * `FAT_TOTAL_G` * `CARBOHYDRATE_G` See [Nutritional Fields Reference](/technical-references/nutritional_fields) for all available fields or check the [API Reference](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3.datamodels/-nutritional-field/index.html) for Kotlin enum values. ## Managing Nutrition Records ### List Records by Date Range Retrieve all nutrition records within a specified date range: ```kotlin theme={null} import java.time.Instant import java.time.temporal.ChronoUnit val endDate = Instant.now() val startDate = endDate.minus(7, ChronoUnit.DAYS) try { val records = spikeConnection.getNutritionRecords( from = startDate, to = endDate ) for (record in records) { val consumedAt = record.consumedAt?.toString() ?: "Unknown date" val size = record.servingSize ?: 0.0 val unit = record.unit?.value ?: "g" println("$consumedAt: ${record.dishName ?: "Unknown"} - $size$unit") } } catch (e: Exception) { println("Failed to fetch records: ${e.message}") } ``` ### Get a Specific Record Retrieve a single nutrition record by its ID: ```kotlin theme={null} try { val record = spikeConnection.getNutritionRecord(id = recordId) if (record != null) { println("Dish: ${record.dishName ?: "Unknown"}") println("Nutri-Score: ${record.nutriScore ?: "N/A"}") // Access nutritional values record.nutritionalFields?.get("energy_kcal")?.let { calories -> println("Calories: $calories kcal") } // Access ingredients if included record.ingredients?.forEach { ingredient -> println("- ${ingredient.name}: ${ingredient.servingSize}${ingredient.unit.value}") } } else { println("Record not found") } } catch (e: Exception) { println("Failed to fetch record: ${e.message}") } ``` ### Update Serving Size Adjust the serving size of an existing record. All nutritional values are automatically recalculated proportionally: ```kotlin theme={null} try { val updatedRecord = spikeConnection.updateNutritionRecordServingSize( id = recordId, servingSize = 200.0 // New serving size in grams ) println("Updated serving size: ${updatedRecord.servingSize ?: 0}${updatedRecord.unit?.value ?: "g"}") println("Recalculated calories: ${updatedRecord.nutritionalFields?.get("energy_kcal") ?: 0}") } catch (e: Exception) { println("Failed to update record: ${e.message}") } ``` ### Delete a Record Permanently remove a nutrition record (success status is returned regardless record is found or not): ```kotlin theme={null} try { spikeConnection.deleteNutritionRecord(id = recordId) println("Record deleted successfully") } catch (e: Exception) { println("Failed to delete record: ${e.message}") } ``` ## Response Data ### `NutritionRecord` The `NutritionRecord` data class contains the analysis results: ```kotlin theme={null} data class NutritionRecord( /** Report record ID */ val recordId: UUID, /** Processing status */ val status: NutritionRecordStatus, /** Detected dish name */ val dishName: String?, /** Detected dish description */ val dishDescription: String?, /** Dish name translated to target language */ val dishNameTranslated: String?, /** Dish description translated to target language */ val dishDescriptionTranslated: String?, /** Nutri-Score known as the 5-Colour Nutrition label (A-E) */ val nutriScore: String?, /** Reason for processing failure */ val failureReason: String?, /** Serving size in metric units */ val servingSize: Double?, /** Metric unit (g for solids, ml for liquids) */ val unit: NutritionalUnit?, val nutritionalFields: Map?, /** List of detected ingredients with nutritional information */ val ingredients: List?, /** Upload timestamp in UTC */ val uploadedAt: Instant, /** Update timestamp in UTC */ val modifiedAt: Instant, /** The UTC time when food was consumed */ val consumedAt: Instant? ) ``` ### `NutritionRecordStatus` ```kotlin theme={null} enum class NutritionRecordStatus(val value: String) { PENDING("pending"), PROCESSING("processing"), COMPLETED("completed"), FAILED("failed"), UNKNOWN("_unknown") // Unknown value was sent from API. SDK should be updated to use the newest API responses. } ``` ### `NutritionalUnit` ```kotlin theme={null} enum class NutritionalUnit(val value: String) { G("g"), // grams MG("mg"), // milligrams MCG("mcg"), // micrograms ML("ml"), // milliliters KCAL("kcal"), // kilocalories UNKNOWN("_unknown") // Unknown value was sent from API. SDK should be updated to use the newest API responses. } ``` ### `NutritionRecordIngredient` ```kotlin theme={null} data class NutritionRecordIngredient( /** Ingredient name using LANGUAL standard terminology */ val name: String, /** Ingredient name translated to target language */ val nameTranslated: String?, /** Serving size in metric units */ val servingSize: Double, /** Metric unit (g for solids, ml for liquids) */ val unit: NutritionalUnit, val nutritionalFields: Map? ) ``` ### `NutritionalField` Use this enum to specify which nutritional fields to include in the analysis: ```kotlin theme={null} enum class NutritionalField(val value: String) { ENERGY_KCAL("energy_kcal"), CARBOHYDRATE_G("carbohydrate_g"), PROTEIN_G("protein_g"), FAT_TOTAL_G("fat_total_g"), FAT_SATURATED_G("fat_saturated_g"), FAT_POLYUNSATURATED_G("fat_polyunsaturated_g"), FAT_MONOUNSATURATED_G("fat_monounsaturated_g"), FAT_TRANS_G("fat_trans_g"), FIBER_TOTAL_DIETARY_G("fiber_total_dietary_g"), SUGARS_TOTAL_G("sugars_total_g"), CHOLESTEROL_MG("cholesterol_mg"), SODIUM_MG("sodium_mg"), POTASSIUM_MG("potassium_mg"), CALCIUM_MG("calcium_mg"), IRON_MG("iron_mg"), MAGNESIUM_MG("magnesium_mg"), PHOSPHORUS_MG("phosphorus_mg"), ZINC_MG("zinc_mg"), VITAMIN_ARAE_MCG("vitamin_a_rae_mcg"), VITAMIN_CMG("vitamin_c_mg"), VITAMIN_DMCG("vitamin_d_mcg"), VITAMIN_EMG("vitamin_e_mg"), VITAMIN_KMCG("vitamin_k_mcg"), THIAMIN_MG("thiamin_mg"), RIBOFLAVIN_MG("riboflavin_mg"), NIACIN_MG("niacin_mg"), VITAMIN_B6MG("vitamin_b6_mg"), FOLATE_MCG("folate_mcg"), VITAMIN_B12MCG("vitamin_b12_mcg") } ``` ## Error Handling All nutrition methods are suspending functions that can throw exceptions. Always wrap calls in try-catch blocks: ```kotlin theme={null} import com.spikeapi.SpikeExceptions try { val record = spikeConnection.analyzeNutrition( image = image, consumedAt = Instant.now(), config = null ) // Handle success } catch (e: SpikeExceptions.SpikeException) { println("Spike error: ${e.message}") } catch (e: SpikeExceptions.NetworkException) { println("Network error: ${e.message}") } catch (e: SpikeExceptions.AuthenticationException) { println("Authentication failed: ${e.message}") } catch (e: Exception) { println("Unexpected error: ${e.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: 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: ```kotlin theme={null} // ❌ Don't request everything "just in case" val config = NutritionalAnalysisConfig( analysisMode = null, countryCode = null, languageCode = null, includeNutriScore = true, includeDishDescription = true, includeIngredients = true, includeNutritionFields = NutritionalField.entries // All 29 fields ) // ✅ Request only what you need val config = NutritionalAnalysisConfig( includeDishDescription = true, includeNutritionFields = listOf( NutritionalField.ENERGY_KCAL ) ) ``` ### 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: ```kotlin theme={null} val record = spikeConnection.getNutritionRecord(id = recordId) if (record == null) { // Handle not found return } if (record.status != NutritionRecordStatus.COMPLETED) { if (record.status == NutritionRecordStatus.FAILED) { // Handle failure println("Failed: ${record.failureReason}") } else { // Still processing println("Status: ${record.status}") } 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: ```kotlin theme={null} object NutritionConfig { val standard = NutritionalAnalysisConfig( analysisMode = NutritionRecordAnalysisMode.PRECISE, countryCode = null, languageCode = null, includeNutriScore = true, includeDishDescription = null, includeIngredients = true, includeNutritionFields = listOf( NutritionalField.ENERGY_KCAL, NutritionalField.PROTEIN_G, NutritionalField.FAT_TOTAL_G, NutritionalField.CARBOHYDRATE_G, NutritionalField.FIBER_TOTAL_DIETARY_G ) ) } // Usage val record = spikeConnection.analyzeNutrition( image = image, consumedAt = Instant.now(), config = NutritionConfig.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 # Android SDK Overview Source: https://docs.spikeapi.com/sdk-docs/android/overview Overview of SDK-based integrations with health platforms including common principles, integration creation process, and data extraction methods for mobile health data management. # Common principles SDK-based integrations (Apple Health Kit, Android Health Connect, and Samsung Health Data) have a few common principles that separate them from all other providers. * Data is stored only on the device (phone) therefore, requires to be actively extracted * Permission control is very gradual (each metric reading requires approval) * Data becomes available instantly as it's recorded * Integration requires active management through SDKs # Creating integrations Creating these integrations does not follow the OAuth path. Meaning * there will be no redirecting to the provider authorization page, * and therefore no callback to URL after permissions are granted. * Permissions are granted locally, on the phone itself, by triggering SDK methods. * Permissions are also managed by default OS schemas, application users navigating settings menus, are granted for application package (reinstalling the app might require reauthorization), can be revoked or ignored when requested by OS based on their policies. The schema below should explain the flow and explain the sequence of events, the moment integration gets created. # Data extraction Data stored only on the user's mobile device's local hardware (encrypted at rest). To make data available over API, first you must select and call SDK functions dedicated to sleep, workouts or other metrics reading. The schema below should explain the flow and explain the sequence of events, the moment when data becomes available for reading over API and SDK. # Events sequence schema SDK integration lifecycle # Android SDK Setup Source: https://docs.spikeapi.com/sdk-docs/android/setup This document provides a setup guide of the Spike SDK for Android platform. ## Version **Current Android(Kotlin) SDK Version:** `4.7.12` ## Resources * Android Package: [Available here](https://gitlab.com/spike_api/spike-android-sdk/-/packages/40880707) * API Reference for Spike SDK: [Available here](https://spike_api.gitlab.io/spike-android-sdk/index.html) * Example app: [Available here](https://gitlab.com/spike_api/public/spike-sdk-examples/-/tree/master/android-v3?ref_type=heads) ## Requirements * **Android Version**: `9.0+` (Level 28, P, Pie) * `SpikeSDK` is compiled using Kotlin 1.9 ## Setup Guide To add the SDK to your project, you have to add the following to your project's `build.gradle` file in the repositories block. ```gradle theme={null} allprojects { repositories { // Other repositories maven { url 'https://gitlab.com/api/v4/projects/43396247/packages/maven' } } } ``` After that, add the following to your app's `build.gradle` file in the dependencies block. ```text theme={null} dependencies { // Other dependencies implementation "com.spikeapi.sdk:spike-sdk:4.7.12" } ``` ### Android Permissions Include the necessary health permissions in your AndroidManifest.xml to fully leverage the Spike SDK and access data from apps integrated with Health Connect. Please refer to [this guide](https://developer.android.com/health-and-fitness/guides/health-connect/get-started#declare-permissions) for details on the required permissions. **Note**: Only request permissions essential to your app’s functionality. Requesting unused permissions may lead to Play Store rejections. ```xml theme={null} ``` XML extensive permissions ```xml theme={null} ``` Add an intent filter to your activity definition so that you can request the permissions at runtime. ```xml theme={null} ``` To handle Android 14 you also need to add activity-alias to your AndroidManifest.xml It is just a wrapper for the activity that requests permissions so no real activity is necessary. ```xml theme={null} ``` ## Compatibility There is a known incompatibility with Firebase libraries, as their latest versions are compiled using Kotlin 2. Spike SDK has been tested and works best with Firebase BOM up to version 33.x.x. # Android SDK Usage Guide Source: https://docs.spikeapi.com/sdk-docs/android/usage-guide Start getting Spike data in 3 steps using Spike SDK for Android platform. ## Step 1: Create a Spike Connection To set up the Spike SDK create `SpikeConnectionV3` instance with your Spike application id, application user id and signature unique to each of your application users (more on generating signatures [here](/api-docs/authentication)): ```kotlin theme={null} val spikeConnection = SpikeConnectionAPIv3.createConnection( applicationId = 1000, signature = "signature", endUserId = "user-id", context = context ) ``` ## Step 2: Ask User for Permissions If you want to read data from Android Health Connect, you have to ensure the user gives your app permissions. First, you have to check if Health Connect is available on users' phone: ```kotlin theme={null} val hcAvailability = spikeConnection.checkHealthConnectAvailability() ``` where ```kotlin theme={null} public enum class HealthConnectAvailabilityStatus(public val value: String) { /** * The Health Connect SDK is unavailable on this device at the time. This can be due to the * device running a lower than required Android Version. Apps should hide any integration * points to Health Connect in this case. */ NOT_INSTALLED("NOT_INSTALLED"), /** * The Health Connect SDK APIs are currently unavailable, the provider is either not installed * or needs to be updated. Apps may choose to redirect to package installers to find a suitable * APK. */ UPDATE_REQUIRED("UPDATE_REQUIRED"), /** * The Health Connect SDK APIs are available. */ INSTALLED("INSTALLED"), } ``` If an update is required, you can use Spike helper to open Play Store for user to install Health Connect: ```kotlin theme={null} spikeConnection.openHealthConnectInstallation() ``` If Health Connect is installed, you can get permissions that are needed and a list of already provided permissions: ```kotlin theme={null} // HC integration has to be enabled in Spike SDK connection before // using further methods for reading data or managing permissions: spikeConnection.enableHealthConnectIntegration() val permissionManager = spikeConnection.getHealthConnectPermissionManager() val requiredPermissions = permissionManager.getPermissions( statisticsTypes = setOf(StatisticsType.STEPS, StatisticsType.DISTANCE_TOTAL) ) val grantedPermissions = permissionManager.getGrantedPermissions() ``` If you have missing permissions, you can ask Android to present the user with a modal asking user for permission to read the data. Example for Compose: ```kotlin theme={null} val permissionLauncher = rememberLauncherForActivityResult( permissionManager.getRequestPermissionResultContract(), onResult = { } ) ``` Please note that users might only grant partial permissions. In such cases, it’s up to you to decide whether your app can function effectively with limited access. The Spike SDK itself will still operate even without full permissions; however, it may result in no data being returned in certain scenarios. Conversely, if your app has been granted additional permissions beyond the minimum required for specific data types, we may enhance certain entries by incorporating data sourced from other types (e.g., identifying manually entered data). You can now use `StatisticsFilter(providers = listOf(Provider.HEALTH_CONNECT))` to specifically retrieve data from Health Connect. Alternatively, you can omit the providers parameter entirely and allow Spike to choose the most suitable data source based on your request. ## Step 3: Get Data The maximum permitted query date range is 90 days There are four types of data you can retrieve from Spike: * **[Statistics](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3/-spike-connection-a-p-iv3/get-statistics.html)** are calculated values derived from records. * **[Activities](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3/-spike-connection-a-p-iv3/get-activities.html)** are data about user's activities or workouts. * **[Sleep](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3/-spike-connection-a-p-iv3/get-sleep.html)** is data about user's sleep. * **[Records](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3/-spike-connection-a-p-iv3/get-records.html)** consist of the raw data points collected from user devices or applications. ### Statistics Get daily statistics for steps and total distance from health Connect: ```kotlin theme={null} val dailyStatistics = spikeConnection.getStatistics( types = setOf(StatisticsType.STEPS, StatisticsType.DISTANCE_TOTAL), from = LocalDate.now().minusWeeks(1).atStartOfDay(ZoneId.systemDefault()).toInstant(), to = Instant.now(), interval = StatisticsInterval.DAY, filter = StatisticsFilter(providers = listOf(Provider.HEALTH_CONNECT)) ) ``` Reference: * [StatisticType](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3.datamodels/-statistics-type/index.html) * [StatisticsInterval](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3.datamodels/-statistics-interval/index.html) * [StatisticsFilter](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3.datamodels/-statistics-filter/index.html) * [Statistic](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3.datamodels/-statistic/index.html) ### Records Get all records we have from Garmin provider: ```kotlin theme={null} val records = spikeConnection.getRecords( types = setOf(MetricType.STEPS_TOTAL, MetricType.CALORIES_BURNED_TOTAL), from = LocalDate.now().minusWeeks(1).atStartOfDay(ZoneId.systemDefault()).toInstant(), to = Instant.now(), filter = StatisticsFilter(providers = listOf(Provider.HEALTH_CONNECT)) ) ``` Reference: * [MetricType](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3.datamodels/-metric-type/index.html) * [StatisticsFilter](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3.datamodels/-statistics-filter/index.html) * [Record](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3.datamodels/-record/index.html) # Android SDK for Samsung Health Source: https://docs.spikeapi.com/sdk-docs/android/usage-guide-shd Start getting Spike data in 3 steps using Spike SDK for Samsung Health Data on Android platform. ## Requirements and Limitations Samsung Health Data is available on Android devices only! * Samsung Health Data SDK runs on devices with Android 10 (API level 29) or above. It is available on all Samsung smartphones and non-Samsung Android smartphones. * Samsung Health Data SDK works with Samsung Health. Samsung Health version 6.30.2 or higher is required. * Samsung Health Data SDK supports Java 17 or higher version. * The emulator is not supported. * Data obtained using Samsung Health Data SDK is for fitness and wellness information only. It is not for the diagnosis or treatment of any medical condition. ## Step 1: Create a Spike Connection If you already set up [Health Connect integration](/sdk-docs/android/usage-guide) in your app, you should [skip this step](#step-2%3A-ask-user-for-permissions) and use the same `SpikeSDK` connection object. To set up the Spike SDK create `SpikeConnectionV3` instance with your Spike application id, application user id and signature unique to each of your application users (more on generating signatures [here](/api-docs/authentication)): ```kotlin theme={null} val spikeConnection = SpikeConnectionAPIv3.createConnection( applicationId = 1000, signature = "signature", endUserId = "user-id", context = context ) ``` ## Step 2: Ask User for Permissions If you want to read data from Samsung Health, you have to ensure the user gives your app permissions. First, you have to check if Samsung Health is available on users' phone using [checkSamsungHealthDataAvailability](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3/-spike-connection-a-p-iv3/check-samsung-health-data-availability.html) method: ```kotlin theme={null} val availability = spikeConnection.checkSamsungHealthDataAvailability(activity = activity) ``` where ```kotlin theme={null} public data class SamsungHealthDataAvailability( public val status: SamsungHealthDataAvailabilityStatus, public val errorCode: Int, public val message: String ) public enum class SamsungHealthDataAvailabilityStatus(public val value: String) { /** * Samsung Health is not installed. Ask the user to install it. */ NOT_INSTALLED("NOT_INSTALLED"), /** * The version of Samsung Health is too old. Ask users to update it. */ UPDATE_REQUIRED("UPDATE_REQUIRED"), /** * The Samsung Health Data is installed but is disabled. */ DISABLED("DISABLED"), /** * Samsung Health has been installed, but the user didn't perform an initial process, such as * agreeing to the Terms and Conditions. */ NOT_INITIALIZED("NOT_INITIALIZED"), /** * Samsung Health returned the other error. */ ERROR_OTHER("ERROR_OTHER"), /** * Samsung Health Data is available. */ INSTALLED("INSTALLED"), } ``` If Samsung Health is installed, you can ask the user for permissions using [requestPermissions](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3.samsung/-samsung-health-data-permission-manager/request-permissions.html) method: ```kotlin theme={null} // Samsung Health integration has to be enabled in Spike SDK connection before // using further methods for reading data or managing permissions: spikeConnection.enableSamsungHealthDataIntegration(activity = activity) spikeConnection.getSamsungHealthDataPermissionManager().requestPermissions( statisticsTypes = setOf(StatisticsType.STEPS, StatisticsType.DISTANCE_TOTAL) ) ``` Please note that users might only grant partial permissions. In such cases, it’s up to you to decide whether your app can function effectively with limited access. The Spike SDK itself will still operate even without full permissions; however, it may result in no data being returned in certain scenarios. You can now use `StatisticsFilter(providers = listOf(Provider.SAMSUNG_HEALTH_DATA))` to specifically retrieve data from Samsung Health. Alternatively, you can omit the providers parameter entirely and allow Spike to choose the most suitable data source based on your request. ## Step 3: Get Data There are four types of data you can retrieve from Spike: * **[Statistics](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3/-spike-connection-a-p-iv3/get-statistics.html)** are calculated values derived from records. * **[Activities](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3/-spike-connection-a-p-iv3/get-activities.html)** are data about user's activities or workouts. * **[Sleep](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3/-spike-connection-a-p-iv3/get-sleep.html)** is data about user's sleep. * **[Records](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3/-spike-connection-a-p-iv3/get-records.html)** consist of the raw data points collected from user devices or applications. ### Statistics Get daily statistics for steps and total distance from Samsung Health using the [getStatistics](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3/-spike-connection-a-p-iv3/get-statistics.html) method: ```kotlin theme={null} val dailyStatistics = spikeConnection.getStatistics( types = setOf(StatisticsType.STEPS, StatisticsType.DISTANCE_TOTAL), from = LocalDate.now().minusWeeks(1).atStartOfDay(ZoneId.systemDefault()).toInstant(), to = Instant.now(), interval = StatisticsInterval.DAY, filter = StatisticsFilter(providers = listOf(Provider.SAMSUNG_HEALTH_DATA)) ) ``` References: * [StatisticType](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3.datamodels/-statistics-type/index.html) * [StatisticsInterval](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3.datamodels/-statistics-interval/index.html) * [StatisticsFilter](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3.datamodels/-statistics-filter/index.html) * [Statistic](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3.datamodels/-statistic/index.html) ### Records Get all records we have from Samsung Health using the [getRecords](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3/-spike-connection-a-p-iv3/get-records.html) method: ```kotlin theme={null} val records = spikeConnection.getRecords( types = setOf(MetricType.STEPS_TOTAL, MetricType.CALORIES_BURNED_TOTAL), from = LocalDate.now().minusWeeks(1).atStartOfDay(ZoneId.systemDefault()).toInstant(), to = Instant.now(), filter = StatisticsFilter(providers = listOf(Provider.SAMSUNG_HEALTH_DATA)) ) ``` ## Background Delivery Samsung Health data can be delivered in the background the same way as Apple HealthKit or Android Health Connect. If you want to use background delivery to get Samsung Health data, you have to enable Samsung Health Data integration first: ```kotlin theme={null} spikeConnection.enableSamsungHealthDataIntegration(activity = activity) ``` After enabling Samsung Health Data integration, you can use background delivery normally. See the [background delivery section](/sdk-docs/android/background-delivery) for more details. ## Developer Mode for Testing To test Samsung Health Data integration on your phone, you have to enable developer mode in the Samsung Health app: 1. Tap the ‘⋮’ button of Samsung Health in the top-right. 2. Select Settings > About Samsung Health. 3. Tap the version line region quickly 10 times or more. If you are successful, the Developer mode (Samsung Health Data SDK) button is displayed. 4. Select Developer mode (Samsung Health Data SDK) . 5. Agree with the Notice about usage of the Developer mode. 6. To read data from Samsung Health with Samsung Health Data SDK, turn Developer Mode for Data Read on. After your app is ready for release, you should apply for a Samsung Partnership Agreement. For more information, please contact Spike support. The Samsung Health developer mode is ONLY intended for testing or debugging your app. It is NOT for app users. Do not provide a developer mode guide to app users. ## See Also * [Samsung error codes](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3.samsung/-samsung-health-data-availability/error-code.html) * [SamsungHealthDataPermissionManager](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3.samsung/-samsung-health-data-permission-manager/index.html) * [SamsungHealthDataAvailabilityStatus](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3.samsung/-samsung-health-data-availability-status/index.html) * [SpikeConnectionV3.enableSamsungHealthDataIntegration](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3/-spike-connection-a-p-iv3/enable-samsung-health-data-integration.html) * [SpikeConnectionV3.disableSamsungHealthDataIntegration](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3/-spike-connection-a-p-iv3/disable-samsung-health-data-integration.html) * [SpikeConnectionV3.isSamsungHealthDataIntegrationEnabled](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3/-spike-connection-a-p-iv3/is-samsung-health-data-integration-enabled.html) # Flutter SDK Background Delivery Source: https://docs.spikeapi.com/sdk-docs/flutter/background-delivery Background delivery ensures that data updates are sent to your backend via webhooks, even when the application is in the background or closed. ## iOS ### Important Notes About Background Delivery on iOS * For most data types, the most possible frequency of updates is 1 hour. * iOS can update data more frequently for some data types, for example, vo2 max. * iOS may throttle the frequency of updates for background delivery depending on the app's activity, battery state, etc. * Background delivery is not possible while a device is locked, so it will be executed only when the device is unlocked. * iOS may stop background delivery if it detects that the app is not active for a long time. * The feature is available starting with iOS 15. **Important:** The Spike SDK, along with any other HealthKit applications, cannot guarantee data synchronization on a fixed schedule. The hourly sync interval serves as a guideline rather than a strict requirement enforced by iOS. Consequently, the actual synchronization frequency may vary, occurring hourly, once per day, or during specific system-defined events, such as the conclusion of Sleep Mode or when the device begins charging. ### Setup #### Enable Background Delivery for the Application Target * Open XCode with your ios project * Open the folder of your project in Xcode * Select the project name in the left sidebar * Open the Signing & Capabilities section * Select HealthKit background delivery under the HealthKit section #### Initialization at Application Startup For background delivery to work properly, you need to initialize the Spike SDK at app startup. `AppDelegate.swift`: ```swift theme={null} import SpikeSDK ... override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { ... Spike.configure() ... } ... ``` ## Android ### Important Notes About Background Delivery on Android * Background delivery is scheduled to run every hour, but ultimately, Android decides when the delivery will be executed. * Android may throttle the frequency of updates for background delivery depending on the app's activity, battery state, etc. * Android may stop background delivery if it detects that the app is not active for a long time. * There is a limit of queries that can be done in Health Connect, and it is different for foreground and background reads, so please request only essential data to be delivered in the background. More information in Health Connect documentation. **Important**: The Spike SDK, along with any other applications, cannot guarantee data synchronization on a fixed schedule. The hourly sync interval serves as a guideline rather than a strict requirement enforced by Android. Consequently, the actual synchronization frequency may vary, occurring hourly, once per day, or during specific system-defined events, such as the conclusion of Sleep Mode or when the device begins charging. ### Setup Add the following permission to your `AndroidManifest.xml` (for Google Health Connect): ```xml theme={null} ``` ## Flutter Specific Add `includeBackgroundDelivery: true` when asking for Health Connect permissions: ```dart theme={null} try { final request = await connection.requestHealthPermissions( statisticTypes: [StatisticsType.steps], includeBackgroundDelivery: true, ); } on SpikeException catch (e) { log("Error: $e"); } ``` You can also ask for background delivery permission at the same time as other permissions. Now you can enable background delivery: ```dart theme={null} try { await connection.enableBackgroundDelivery( statisticTypes: [StatisticsType.steps], sleepConfigs: [ SleepConfig(includeMetricTypes: {MetricType.heartrate}) ], ); } on SpikeException catch (e) { log("Error: $e"); } ``` Keep in mind that calling `enableBackgroundDelivery()` will overwrite previous configuration. So you have to call it with all the data types you want in one call. To check current configuration call `getBackgroundDeliveryConfig()` method. To stop background delivery call `disableBackgroundDelivery()` method. ## Samsung Health If Samsung Health Data is enabled before enabling background delivery, `enableBackgroundDelivery` will automatically enable Samsung Health Data background delivery. # Flutter SDK Changelog Source: https://docs.spikeapi.com/sdk-docs/flutter/changelog A changelog documenting version updates and changes of Spike SDK for Flutter platform. ## 4.5.24 * Updated Android SDK to `4.5.22` * Updated iOS SDK to `4.5.21` * Added **backfill** on the v3 connection to upload historical health data for a chosen number of days. Call it after you have granted the relevant permissions (HealthKit on iOS; Health Connect and/or Samsung Health Data on Android). ```dart theme={null} await spikeConnection.backfill( statisticTypes: statisticTypes, metricTypes: metricTypes, activityConfigs: [activityConfig], sleepConfigs: [sleepConfig], ); ``` * **iOS (HealthKit):** added `requestPermissionsFromHealthKitAndBackfill` — requests read permissions for the given categories, then starts a backfill. Data for today and yesterday is pushed before the call resolves; older days continue in the background. ```dart theme={null} await spikeConnection.requestPermissionsFromHealthKitAndBackfill( statisticTypes: statisticTypes, metricTypes: metricTypes, activityConfigs: [activityConfig], sleepConfigs: [sleepConfig], ); ``` * **Android (Samsung Health Data):** added `requestPermissionsFromSamsungHealthDataAndBackfill` — enables Samsung Health Data integration, requests permissions for the given categories, then starts a backfill. * **Android (Health Connect):** added `requestPermissionsFromHealthConnectAndBackfill` — enables Health Connect integration, requests permissions for the given categories, then starts a backfill. * **iOS:** If after updating you get errors while building iOS app please do following: 1. `rm -rf ios/Pods/ ios/Podfile.lock` - delete old pods 2. `cd ios; pod install --repo-update` - install updated pods 3. Open project in Xcode and do a full clean: `Product -> Clean build folder` or `Shift + Command + K` ## 4.5.14 * Added `RecordConfig` for configuring records queries: * `includeSamples` - whether to include samples (raw data points) in the response * Updated `getRecords` to accept an optional `RecordConfig` parameter: ``` spikeConnection.getRecords( ofTypes: metricTypes, from: startDate, to: endDate, filter: StatisticsFilter(excludeManual: true), config: RecordConfig(includeSamples: true), ) ``` * Fixed ActivityConfig parsing * Updated Android SDK to `4.5.12` * Updated iOS SDK to `4.5.11` * IMPORTANT! If after updating you get errors while building iOS app please do following: 1. `rm -rf ios/Pods/ ios/Podfile.lock` - delete old pods 2. `cd ios; pod install --repo-update` - install updated pods 3. Open project in XCode 26 and do a full clean: `Product -> Clean build folder` or `Shift + Command + K` ## 4.4.14 * Updated Android SDK to `4.4.12` * Updated iOS SDK to `4.4.11` * Updated `Unit` enum with new values: * Added: `miles`, `mPerSec`, `fahrenheit`, `ms`, `seconds`, `degrees`, `sleepStage`, `secPerM`, `rpm`, `spm`, `breathsPerMin`, `g`, `lbs`, `st`, `mmHg`, `mLPerKgPerMin`, `uV`, `mgPerDl`, `w` * Removed: `kmh`, `kmPerMin` * Improved data communication with API * Enhanced the `getSleep` endpoint. Important: * Dates provided are now interpreted as dates only; the time component is ignored when querying the API. * The primary night sleep period is associated with the calendar date on which it ends. ## 4.3.184 * Updated Android SDK to `4.3.142` * Internal improvements * Updated iOS SDK to `4.3.161` * Better Nutrition Facts Label Recognition response parsing ## 4.3.174 * MetricType: added namespace with metric allowlists for API requests: * `MetricType.availableForRecordsRequest` – list of metric types available for records requests * `MetricType.availableForActivitiesRequest` – list of metric types available for activities requests * `MetricType.availableForSleepRequest` – list of metric types available for sleep requests * `MetricType.availableForPersonRequest` – list of metric types available for person requests * Updated iOS SDK to `4.3.151`: * Improved data push to API * Fixed workout digest generation for caching * Improved client cache configuration ## 4.3.164 * Updated Android SDK to `4.3.132`: * Samsung Health Data Integration - Major Expansion: * Added support for reading and pushing the following data types from Samsung Health: * Workouts (exercises) * Sleep data * Body composition data (weight, height, BMI, body fat) * User profile data (weight, height, birth date, gender) * Blood pressure measurements * Skin temperature measurements * Heart rate measurements * Blood oxygen saturation (SpO2) measurements * Body temperature measurements * More efficient Samsung Health Data pushes * Large Samsung Health Data pushes are now automatically split into smaller chunks for reliable delivery * Large Health Connect pushes are now automatically split into smaller chunks for reliable delivery ## 4.3.154 * Added Nutrition Facts Label Recognition for extracting nutritional information from label photos. For more information please visit our [documentation](https://docs.spikeapi.com). * `recognizeNutritionFactsLabel({required String imageBase64, NutritionFactsLabelRecognitionConfig? config})` - Analyze a nutrition facts label image and extract nutritional data * Added methods for manual nutrition record management: * `createNutritionRecord(NutritionRecord nutritionRecord)` - Insert a new nutrition record with custom data * `replaceNutritionRecord(NutritionRecord nutritionRecord)` - Replace an existing nutrition record completely * Added `NutritionRecordUtils` for creating and manipulating nutrition records: * `NutritionRecordUtils.create({...})` - Create a new nutrition record with specified properties * `record.withIngredient(ingredient)` - Add an ingredient to a record * `record.withoutIngredient(ingredient)` - Remove an ingredient from a record * `record.withServingSize(servingSize, unit)` - Update serving size and unit * Added `NutritionRecordIngredientUtils` for creating and manipulating ingredients: * `NutritionRecordIngredientUtils.create({...})` - Create a new nutrition record ingredient * `ingredient.withNutritionalField(nutritionalField, value)` - Set a nutritional field value * `ingredient.withoutNutritionalField(nutritionalField)` - Remove a nutritional field * `ingredient.withScaledServingSize(newServingSize)` - Scale serving size and nutritional values proportionally * Nutrition AI methods now return `NutritionRecordAnalysisResult` instead of `NutritionRecord`. This separates processing metadata (status, failure reason) from the actual nutrition data. The following methods are affected: * `analyzeNutrition()` → returns `Future` * `getNutritionRecords()` → returns `Future>` * `getNutritionRecord()` → returns `Future` * `updateNutritionRecordServingSize()` → returns `Future` * `createNutritionRecord()` → returns `Future` * `replaceNutritionRecord()` → returns `Future` The `NutritionRecordAnalysisResult` contains: * `recordId` - The record ID * `status` - Processing status (pending, processing, completed, failed, updated) * `failureReason` - Reason for failure (if status is failed) * `uploadedAt` - Upload timestamp * `nutritionRecord` - The actual `NutritionRecord` (null if status is not completed/updated) * Update Android SDK to `4.3.122` * Update iOS SDK to `4.3.141` * IMPORTANT! If after updating you get errors while building iOS app please do following: 1. `rm -rf ios/Pods/ ios/Podfile.lock` - delete old pods 2. `cd ios; pod install --repo-update` - install updated pods 3. Open project in XCode 26 and do a full clean: `Product -> Clean build folder` or `Shift + Command + K` ## 4.3.144 * Added Nutrition AI features for analyzing food images. For more information please visit our [documentation](https://docs.spikeapi.com). * `analyzeNutrition(imageBase64, consumedAt, config)` - Analyze food image synchronously and wait for results * `submitNutritionForAnalysis(imageBase64, consumedAt, config)` - Analyze food image asynchronously, returns record ID * `getNutritionRecords(from, to)` - Retrieve nutrition records for a date range * `getNutritionRecord(id)` - Get a specific nutrition record by ID * `deleteNutritionRecord(id)` - Delete a nutrition record * `updateNutritionRecordServingSize(id, servingSize)` - Update serving size for a nutrition record * Added new `Provider`: * luna * Added new `ProviderSource` types: * fitbitSkinTemperatureSummary * garminWellnessSkinTemperatureSummary * lunaSleep * Removed `ProviderSource` types: * fitbitUserActivitiesList * fitbitUserActivitiesDate * fitbitUserActivitiesHeartDate * fitbitUserActivitiesHeartDateInterday * withingsMeasureGetWorkouts * withingsMeasureGetActivity * withingsMeasureGetMeas * Removed `ActivityTag`: * onBicycle * Removed `Provider`: * wahoo ## 4.3.134 * Update Android SDK to `4.3.102` * Enable Health Connect integration in admin console right after enabling integration * Enable Samsung Health Data integration in admin console right after enabling integration * Update iOS SDK to `4.3.121` * Enable HealthKit integration in admin console right after requesting permissions ## 4.3.124 * Added ability to read mindfulness activities: ``` spikeConnection.getActivities( config: ActivityConfig(activityCategories: {ActivityCategory.mindfulness}) from: dateFrom, to: dateTo ) ``` * Added ability to check Android Health Connect features availability: ``` const isMindfulessAvailable = spikeConnection.isHealthConnectFeatureAvailable(HealthConnectFeature.FEATURE_MINDFULNESS_SESSION) ``` * Added new ActivityTag types: * mindfulness * Added new ActivityType types: * ecgMeasurement * triathlon * biathlon * duathlon * rollerblading * skateboarding * skating * calisthenics * weightLifting * canoeing * floorball * jiuJitsu * diving * orienteering * bootcamp * motorsports * horseriding * paragliding * multisport * bloodTest * mindfulnessSession * Added new ProviderSource types: * appleHealthkitMindfulness * healthConnectMindfulnessSession * Added a new ActivityCategory type: * mindfulness * Updated Android SDK to `4.3.92` * Updated Health connect to `1.1.0-rc03` * Updated compileSdk to `36` * Updated gradle to `8.13` * `android.permission.health.READ_MINDFULNESS` user permission should be added to `AndroidManifest.xml` file * Updated iOS SDK to `4.3.111` ## 4.3.114 * Updated Android SDK to `4.3.82`: * Workaround for the Android 15 bug to make background delivery more resilient * More efficient pushes to Spike API ## 4.3.104 * Updated Android SDK to `4.3.72`: * Allow reading `SPO2` and `SWIMMING_LENGTHS` in `getActivities` * Allow reading `SPO2` and `SKIN_TEMPERATURE` in `getSleeps` * Updated iOS SDK to `4.3.101`: * Improved how sleep data is read for sleep score ## 4.3.94 * Updated Android SDK to `4.3.62`: * Optimized background delivery ## 4.3.84 * Added `.coros` `Provider` * Updated `ProviderSource` enum * Added statistics: `stressScore`, `recoveryScore`, `activityScore` * Updated Android SDK to `4.3.52`: * Added `Provider.COROS` * Updated `ProviderSource` enum * Added new Statistic types: * STRESS\_SCORE * RECOVERY\_SCORE * ACTIVITY\_SCORE * Added new Metric types for `getSleep` request: * SLEEP\_DURATION * SLEEP\_DURATION\_AWAKE * SLEEP\_DURATION\_DEEP * SLEEP\_DURATION\_LIGHT * SLEEP\_DURATION\_NAP * SLEEP\_DURATION\_REM * SLEEP\_EFFICIENCY * SLEEP\_INTERRUPTIONS * SLEEP\_LATENCY * SLEEP\_SCORE * Updated iOS SDK to `4.3.91`: * Added `.coros` `Provider` * Updated `ProviderSource` enum * Improved the way data is sent to API to reduce the size and speed of calls to Spike * Added new Statistic types: * stressScore * recoveryScore * activityScore * Added new Metric types for `getSleep` request: * sleepDuration * sleepDurationAwake * sleepDurationDeep * sleepDurationLight * sleepDurationNap * sleepDurationRem * sleepEfficiency * sleepInterruptions * sleepLatency * sleepScore * Better keychain compatibility NOTE: If you use the keychain in your app with a service name equal to your bundle identifier, please check if it contains key named `spikeApiToken` and delete it. ## 4.3.74 * Added new `SpikeSDKV3.setLogCallback()` method to receive logs from the native SDK. Example usage: ```dart theme={null} await SpikeSDKV3.setLogCallback( callback: (level, message) { print("[SpikeSDK.${level.toJson()}] $message"); }, ); ``` * Added a new metric type: * `glucose` * Updated Android SDK to `4.3.42`: * New `MetricType.GLUCOSE` available in both Health Connect and Samsung Health * Update Samsung Health Data SDK to version 1.0.0 (available only on Android 29+) * Updated iOS SDK to `4.3.81`: * Added `MetricType`: `.glucose` ## 4.3.64 * Updated Android SDK to `4.3.93`: * Deprecated the old Spike `SpikeSDK` * Switch to protobuf java-lite 3.25.5 for better compatibility with firebase * Updated iOS SDK to `4.3.71`: * Deprecated the old Spike `SpikeSDK` * Added `disableHealthKitIntegration` and `isHealthKitIntegrationEnabled` functions in spike connection ## 4.3.54 * Added new providers added: dexcom, freestyleLibre, huawei, strava * Added .distanceSwimming into the list of metrics available in activities * Updated native iOS SDK Version to `4.3.61` * Updated native Android SDK Version to `4.3.22` ## 4.3.44 * Added Samsung Health Integration on Android * Updated native Android SDK Version to `4.3.12`: * New statistics for Health Connect: * HEARTRATE * HEARTRATE\_MAX * HEARTRATE\_MIN * New metrics for Health Connect: * HEARTRATE * HEARTRATE\_MAX * HEARTRATE\_MIN * Improved transport protocol for even faster requests to Spike API * Added Samsung Health Integration for: * `StatisticType`s: * STEPS * DISTANCE\_TOTAL * CALORIES\_BURNED\_ACTIVE * CALORIES\_BURNED\_TOTAL * CALORIES\_BURNED\_BASAL * Added new `Provider`: * SAMSUNG\_HEALTH\_DATA * Added new `ProviderSource`: * SAMSUNG\_HEALTH\_DATA\_AGGREGATION * Renamed `MetricType`s: * SLEEP\_BREATHING\_RATE to BREATHING\_RATE * SLEEP\_BREATHING\_RATE\_MIN to BREATHING\_RATE\_MIN * SLEEP\_BREATHING\_RATE\_MAX to BREATHING\_RATE\_MAX * Updated native iOS SDK Version to `4.3.51`: * Added new `Provider`: * samsungHealthData * Added new `ProviderSource`: * samsungHealthDataAggregation * Renamed `MetricTypes`: * `sleepBreathingRate` to `breathingRate` * `sleepBreathingRateMin` to `breathingRateMin` * `sleepBreathingRateMax` to `breathingRateMax` ## 4.3.34 * Updated native iOS SDK Version to `4.3.41` * IntegrationInitConfig is now codable * Additional check in `getIntegrationInitUrl` for email in ultrahuman integration * Updated native Android SDK Version to `4.2.82` * Added `IntegrationInitConfigUtils` for usage in Flutter and React Native libraries * Additional check in `getIntegrationInitUrl` for email in ultrahuman integration * New provider: `ultrahuman` When integrating with Ultrahuman, you have to provide ultrahuman user email in `IntegrationInitConfig.providerUserIdentifier`. Example: ``` spikeConnection.getIntegrationInitUrl(provider: .ultrahuman, config: IntegrationInitConfig(providerUserIdentifier: "user@mail.com")) ``` ## 4.3.24 * Updated native iOS SDK Version to `4.3.31` * Improve how statistics are read from HealthKit * New metric types: * bodyFat * bodyFatMax * bodyFatMin * bodyBoneMass * bodyMassIndex * bloodPressureSystolic * bloodPressureSystolicMax * bloodPressureSystolicMin * bloodPressureDiastolic * bloodPressureDiastolicMax * bloodPressureDiastolicMin * New fields in `UserProperties`: * bodyBoneMass * bodyFat * bodyMassIndex * Updated `getIntegrationInitUrl`. Now it accepts `IntegrationInitConfig` config object where you can pass: * `redirectUri`: will override the one set in admin console * `state`: when the authorization server redirects back to the client, it includes the `state` value originally sent * `providerUserIdentifier`: at the moment used (and required) only when integrating with ultrahuman * New provider: `ultrahuman` When integrating with Ultrahuman, you have to provide ultrahuman user email in `IntegrationInitConfig.providerUserIdentifier`. Example: ``` spikeConnection.getIntegrationInitUrl(provider: .ultrahuman, config: IntegrationInitConfig(providerUserIdentifier: "user@mail.com")) ``` * Improved transport protocol for background delivery * Updated native Android SDK Version to `4.2.72` * Added new `MetricType`s: * BODY\_FAT * BODY\_FAT\_MAX * BODY\_FAT\_MIN * BODY\_BONE\_MASS * BODY\_MASS\_INDEX * BLOOD\_PRESSURE\_SYSTOLIC * BLOOD\_PRESSURE\_SYSTOLIC\_MIN * BLOOD\_PRESSURE\_SYSTOLIC\_MAX * BLOOD\_PRESSURE\_DIASTOLIC * BLOOD\_PRESSURE\_DIASTOLIC\_MIN * BLOOD\_PRESSURE\_DIASTOLIC\_MAX * New fields in `UserProperties`: * BODY\_FAT * BODY\_BONE\_MASS * BODY\_MASS\_INDEX * Updated `getIntegrationInitUrl`. Now it accepts `IntegrationInitConfig` config object where you can pass: * `redirectUri`: will override the one set in admin console * `state`: when the authorization server redirects back to the client, it includes the `state` value originally sent * `providerUserIdentifier`: at the moment used (and required) only when integrating with ultrahuman * Added new provider: `ultrahuman` When integrating with Ultrahuman, you have to provide Ultrahuman user email in `IntegrationInitConfig.providerUserIdentifier`. Example: ``` spikeConnection.getIntegrationInitUrl(provider = Provider.ULTRAHUMAN, config = IntegrationInitConfig(providerUserIdentifier = "user@mail.com")) ``` ## 4.3.14 * Updated native iOS SDK Version to `4.3.11` * Improved transport protocol for even faster requests to Spike API * Added new statistic metrics: heartrate, heartrateMax, heartrateMin, heartrateResting * Updated native Android SDK Version to `4.2.62` * Added new statistic metrics: * HEARTRATE\_RESTING * SLEEP\_DURATION\_TOTAL * Added new statistic metrics (only from non-HealthConnect providers): * HEARTRATE * HEARTRATE\_MIN * HEARTRATE\_MAX * Metric types updates * Better proguard settings for uniqueness of generated class names * Fixed statistics when in different time zones * Added consumer proguard rules ## 4.2.74 * Removed outdated example. Please follow [Flutter SDK Usage Guide](/sdk-docs/flutter/usage-guide#step-3%3A-get-data). ## 4.2.64 * Updated readme file ## 4.2.54 * Updated native iOS SDK Version to iOS 4.2.41 * Fixed the date format in JSON push ## 4.2.44 * Fixed JSON parsing of user info response ## 4.2.34 * Added new metrics: * spo2 * bodyTemperature * skinTemperature (available only on getSleep()) * sleepBreathingRate (available only on getSleep()) * sleepBreathingRateMin (available only on getSleep()) * sleepBreathingRateMax (available only on getSleep()) * sleepBreathingRateAvg (available only on getSleep()) * Updated native iOS SDK Version to iOS 4.2.31 * Updated native Android SDK Version to 4.2.42 * Added sleepScore property in the Record type ## 4.2.24 * Added new metric: `VO2MAX` available in `getRecords` and `getActivities` * Changed hrv metric names: `hrvRmssd`, `hrvSdnn` * Added new statistic: `sleepScore` * Updated native iOS SDK Version to iOS 4.2.21 * Added new `getUserProperties` method for reading: weight, height, timezone, birthdate, gender * Updated native Android SDK Version to 4.2.32 * Added new `getUserProperties` method for reading: weight, height, timezone from Health Connect * Added new `getUserProperties` method for reading: birthdate, gender from other providers ## 4.2.14 * Updated native Android SDK Version to 4.2.12 * Updated native iOS SDK Version to 4.2.11 * 🔴 Breaking Change: Update to `SpikeSDKV3.createConnection`. The method `Spike.createConnectionAPIv3({appId:, authToken:, customerEndUserId:})` has been updated to: `SpikeSDKV3.createConnection(applicationId:, signature:, endUserId:)` * The `applicationId` parameter must now be provided as an **`int`**. * The `signature` parameter now requires an **`HMAC-SHA256` signed user ID**. * ⚠️ **Security Notice:** * **Do not store your HMAC signing key within the application itself**, as this poses a security risk. * Instead, generate and provide the signature from your backend. * **Legacy Support**: For **development purposes only**, the previous connection flow remains available under the renamed method: `SpikeSDKV3.createConnectionLegacy(appId:, authToken:, customerEndUserId:)` ## 4.1.14 * Background delivery for both iOS and Android * Added triathlon activity type * Better error messages in iOS SpikeSDK wrapper * Updated native Android SDK Version to 4.1.12 * Add background delivery (see documentation for more information) * Health Connect library updated to 1.1.0-alpha11 * Compile SDK and target SDK updated to 35 * Updated kotlin version to 1.9.25 * Updated other dependencies * Updated gradle to 8.8.0 * Updated native iOS SDK Version to 4.1.11 * Added background delivery * Implemented sending more sleep data ## 4.0.14 * Completely new SDK! Please see our official documentation for more details and usage instructions * Updated native Android SDK Version to 4.0.22 * Updated native iOS SDK Version to 4.0.12 * Added getIntegrationInitUrl method to SpikeConnectionV3: generates integration url for the given provider ## 1.3.3 * Android: Fixed build issue on AGP8+ ## 1.3.2 * Updated native Android SDK Version to 3.1.4 * Added more raw data to calories request ## 1.3.1 * Updated native iOS SDK Version to 2.4.2: * Added customer user id validation * Updated native Android SDK Version to 3.1.1: * Better way to authenticate with API * Added saved session validation before the connection is established * Added customer user id validation ## 1.3.0 * Added ECG handling in iOS. * Added empty results when ECG is asked for on an Android device. * Made connections array in iOS thread-safe. * Updated structure of `SpikeSource` data model. * README updated with ECG data type. * Updated native iOS SDK Version to 2.4.1: * Fix the timezone in case it changes while the app is running. * Better way to authenticate with API. * ECG support. * Add trigger property to extractAndPostData request. * Add cycling parameters to Activities stream data request. ## 1.2.10 * Updated native Android SDK Version. * Updated native iOS SDK Version. * README updated. ## 1.2.9 * Updated native iOS SDK Version. ## 1.2.8 * Updated native Android SDK Version. * Updated native iOS SDK Version. * PROD/DEV environments separation added. * Background delivery handling is documented for the iOS. * Method to disable background delivery introduced. * setEnvironment method removed as environment must be set when creating the connection. ## 1.2.7 * Updated native Android SDK Version. * Updated native iOS SDK Version. ## 1.2.6 * Updated native Android SDK Version. * Updated native iOS SDK Version. ## 1.2.5 * SpikeStepData class name renamed into SpikeStepsData to keep aligned with Dart coding conventions. * Intraday steps data introduced. ## 1.2.4 * Updated native Android SDK Version. ## 1.2.3 * Updated native Android SDK Version. ## 1.2.2 * Updated native Android SDK Version ## 1.2.1 * Updated native Android SDK Version ## 1.2.0 * Updated README with Android 14 set-up guide * Ensured permissions are checked and requested when the user tries to extract the data * Introduced ensurePermissionsAreGrantedV2 method, so it would become possible to get more information about what happened when requesting for permissions, which might be useful for Flutter Android apps * Improved processing of the Health Connect permissions when a user cancels giving permissions, gives them partially, or performs some other unexpected behavior * Updated native iOS SDK Version ## 1.1.1 * Android Native SDK version updated ## 1.1.0 * Updated iOS Native SDK version * Updated Android Native SDK version * Recreated Android Native SDK permissions handling ## 1.0.27 * Updated iOS Native SDK version ## 1.0.26 * Updated iOS Native SDK version ## 1.0.25 * Updated iOS Native SDK version * Updated Android Native SDK version * Added the method `isPackageInstalled` to Android ## 1.0.24 * Updated iOS Native SDK version ## 1.0.23 * Updated iOS Native SDK version * Updated Android Native SDK version * Added more information on exceptions thrown in the README ## 1.0.22 * Updated iOS Native SDK version ## 1.0.21 * Updated iOS Native SDK version ## 1.0.20 * Updated native Android SDK version usage * Made it possible to set the environment when using Android SDK ## 1.0.19 * Added the method `getSpikeEndUserId` into the `SpikeConnection` ## 1.0.18 * Updated Android SDK integration ## 1.0.17 * Removed pack, unpack, since it is clients who need to manage their state * Integrated web hook connections into `SpikeConnection` * Reduced minimum SDK version to 28 * Reduced compile an SDK version to 33 * Updated examples * Updated README * Fully changed the integration with Android SDK ## 1.0.16 * Implemented returning boolean after requesting permissions. ## 1.0.15 * Native wrapping with native Android SDK release build issues resolved * Resolved requesting permissions issues using native Android SDK * Updated README * Updated examples ## 1.0.14 * Native wrapping with native Android SDK finalized ## 1.0.13 * Introduced native wrapping with native Android SDK * Introduced native wrapping with native iOS SDK ## 1.0.12 * Min iOS version reduced for the sleep data ## 1.0.11 * Updated README * Updated examples ## 1.0.10 * Updated README * Ensured dart doc is working ## 1.0.9 * Updated README * Made sure that casting when using connection.extractData() is no longer needed ## 1.0.8 * Updated README * Updated data keys expectations per changed backend ## 1.0.7 * iOS workouts reading problems resolved * Made sure floors are added from the iOS data * Added functionality that allows configuration of the background delivery tasks * Added debouncing for the background delivery tasks of five seconds, so they would not trigger unnecessary sending to the backend * Fixed an issue of reading sleep data. Ensured user is requested for additional permissions that are required to collect the statistics needed regarding sleep * Made sure that in sleep statistics zeroes would be changed into null when no data is read * Updated README with the background delivery example information ## 1.0.6 * Breathing data issues resolved * Workouts reading issues resolved * Updated README * Updated SpikeSDK so that SpikeSDK.initialize() would not be needed as per documentation ## 1.0.5 * Better background delivery configuration into SpikeSDK added ## 1.0.4 * Improved logging * Improved sending data to the backend * Android Health Connect Core recreated using direct API for the Health Connect due to the failure of the Flutter package to properly convert data under the release mode ## 1.0.3 * Added more logging * Fixed IDs usage ## 1.0.2 * Improved logging ## 1.0.1 * Fully rewritten of the communication with the backend * Introduced reading the body data * Improved calculation of time * Added more logging ## 1.0.0 * Complete rewrite of the library to follow the Spike standards required ## 0.0.10 * Sleep data reading and sending support introduced ## 0.0.9 * Activity groups support introduced ## 0.0.8 * Removed some data for summary * Workouts data times rounding added, so conversions would not fail ## 0.0.7 * Updated examples * Summary data reading improved even further * Added the way to send separate identifier data * Updated README ## 0.0.6 * Introduced workout data reading * Improved reading of summary data, and now all the summary data can be read ## 0.0.5 * Background delivery now takes the last three days for the data to be sent * Removed calories, and basal energy burned introduced instead ## 0.0.4 * Background delivery support introduced * Better code organization regarding storing some configured data in the storage * Background delivery interval support introduced * Improvements regarding date and time resolution when resolving or sending the data ## 0.0.3 * Made sure background tasks would take correct intervals to send the data * Added more information to logging * Made sure events logged would be sorted by the date in ascending order ## 0.0.2 * Improved handling of the background tasks registration * Support to set custom task ID introduced to check if background tasks actually work * Support for event tracking introduced. Tracking can be enabled or disabled ## 0.0.1 * Apple HealthKit data reading functionality with the ability to send this data to the server of your choice # Flutter SDK Logging Source: https://docs.spikeapi.com/sdk-docs/flutter/logging Getting logs for troubleshooting and debugging from Spike SDK for Flutter platform. ## Code Examples To receive logs from Spike SDK, use the following code: ```dart theme={null} await SpikeSDKV3.setLogCallback( callback: (level, message) { log("[SpikeSDK.${level.toJson()}] $message"); }, ); ``` Callback will get log level enum and string with a log message. ``` enum LogLevel { verbose, debug, warning, error; } ``` # Flutter SDK Nutrition AI Source: https://docs.spikeapi.com/sdk-docs/flutter/nutrition-ai Analyze food images and retrieve nutritional information using AI-powered analysis in your Flutter 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 Flutter 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 `Future` objects. Use `try-catch` blocks or `.catchError()` handlers for error handling. See [Error Handling](#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. ```dart theme={null} import 'dart:convert'; import 'dart:io'; import 'package:spike_flutter_sdk/spike_flutter_sdk.dart'; // Capture image from camera or gallery and convert to base64 final File imageFile = // ... captured from camera or gallery final bytes = await imageFile.readAsBytes(); final imageBase64 = base64Encode(bytes); try { final record = await spikeConnection.analyzeNutrition( imageBase64: imageBase64, consumedAt: DateTime.now(), config: NutritionalAnalysisConfig( analysisMode: NutritionRecordAnalysisMode.precise, countryCode: 'us', languageCode: 'en', includeNutriScore: true, includeDishDescription: true, includeIngredients: true, includeNutritionFields: [ NutritionalField.energyKcal, NutritionalField.proteinG, NutritionalField.fatTotalG, NutritionalField.carbohydrateG, ], ), ); print('Dish: ${record.dishName ?? "Unknown"}'); print('Serving size: ${record.servingSize ?? 0} ${record.unit?.toJson() ?? "g"}'); print('Calories: ${record.nutritionalFields?["energy_kcal"] ?? 0}'); } catch (e) { print('Analysis failed: $e'); } ``` You can also call with minimal parameters (config is optional): ```dart theme={null} // Using defaults - only imageBase64 is required final 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. ```dart theme={null} try { // Submit image for background processing final recordId = await spikeConnection.submitNutritionForAnalysis( imageBase64: imageBase64, consumedAt: DateTime.now(), config: NutritionalAnalysisConfig( analysisMode: NutritionRecordAnalysisMode.fast, countryCode: null, languageCode: null, includeNutriScore: null, includeDishDescription: null, includeIngredients: true, includeNutritionFields: null, ), ); print('Analysis started. Record ID: $recordId'); // Optionally, poll for results later // Your backend will also receive a webhook when analysis completes } catch (e) { print('Failed to submit: $e'); } ``` #### 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. ```dart theme={null} // Check the status and get results final record = await spikeConnection.getNutritionRecord(id: recordId); if (record != null) { switch (record.status) { case NutritionRecordStatus.completed: print('Analysis complete: ${record.dishName ?? "Unknown"}'); break; case NutritionRecordStatus.processing: print('Still processing...'); break; case NutritionRecordStatus.pending: print('Queued for processing...'); break; case NutritionRecordStatus.failed: print('Analysis failed: ${record.failureReason ?? "Unknown error"}'); break; case NutritionRecordStatus.unknown: print('Unknown status'); break; } } ``` 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. ## Configuration Options Customize the analysis using `NutritionalAnalysisConfig`: ```dart theme={null} final config = NutritionalAnalysisConfig( // 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, ], ); final record = await spikeConnection.analyzeNutrition( imageBase64: imageBase64, consumedAt: DateTime.now(), config: config, ); ``` ### `NutritionalAnalysisConfig` ```dart theme={null} class NutritionalAnalysisConfig { /// A preferred mode for the analysis. Default is precise. final NutritionRecordAnalysisMode? analysisMode; /// Country ISO 3166-1 alpha-2 code in lowercase final String? countryCode; /// Language ISO 639-1 code in lowercase final String? languageCode; /// Include nutri-score label of the food. Default is false. final bool? includeNutriScore; /// Include dish description of the food. Default is false. final bool? includeDishDescription; /// Include ingredients of the food. Default is false. final bool? includeIngredients; /// Include specific nutrition fields in the analysis report. /// By default, carbohydrate_g, energy_kcal, fat_total_g and protein_g will be included. final List? includeNutritionFields; } ``` ### Analysis Modes ```dart theme={null} enum NutritionRecordAnalysisMode { fast, 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. ## Managing Nutrition Records ### List Records by Date Range Retrieve all nutrition records within a specified date range: ```dart theme={null} final now = DateTime.now(); final startDate = now.subtract(const Duration(days: 7)); final endDate = now; try { final records = await spikeConnection.getNutritionRecords( from: startDate, to: endDate, ); for (final record in records) { final consumedAt = record.consumedAt?.toString() ?? 'Unknown date'; final size = record.servingSize ?? 0; final unit = record.unit?.toJson() ?? 'g'; print('$consumedAt: ${record.dishName ?? "Unknown"} - $size$unit'); } } catch (e) { print('Failed to fetch records: $e'); } ``` ### Get a Specific Record Retrieve a single nutrition record by its ID: ```dart theme={null} try { final record = await spikeConnection.getNutritionRecord(id: recordId); if (record != null) { print('Dish: ${record.dishName ?? "Unknown"}'); print('Nutri-Score: ${record.nutriScore ?? "N/A"}'); // Access nutritional values final calories = record.nutritionalFields?['energy_kcal']; if (calories != null) { print('Calories: $calories kcal'); } // Access ingredients if included for (final ingredient in record.ingredients ?? []) { print('- ${ingredient.name}: ${ingredient.servingSize}${ingredient.unit.toJson()}'); } } else { print('Record not found'); } } catch (e) { print('Failed to fetch record: $e'); } ``` ### Update Serving Size Adjust the serving size of an existing record. All nutritional values are automatically recalculated proportionally: ```dart theme={null} try { final updatedRecord = await spikeConnection.updateNutritionRecordServingSize( id: recordId, servingSize: 200.0, // New serving size in grams ); print('Updated serving size: ${updatedRecord.servingSize ?? 0}${updatedRecord.unit?.toJson() ?? "g"}'); print('Recalculated calories: ${updatedRecord.nutritionalFields?["energy_kcal"] ?? 0}'); } catch (e) { print('Failed to update record: $e'); } ``` ### Delete a Record Permanently remove a nutrition record (success status is returned regardless record is found or not): ```dart theme={null} try { await spikeConnection.deleteNutritionRecord(id: recordId); print('Record deleted successfully'); } catch (e) { print('Failed to delete record: $e'); } ``` ## Response Data ### `NutritionRecord` The `NutritionRecord` class contains the analysis results: ```dart theme={null} class NutritionRecord { /// Report record ID final String recordId; /// Processing status final NutritionRecordStatus status; /// Detected dish name final String? dishName; /// Detected dish description final String? dishDescription; /// Dish name translated to target language final String? dishNameTranslated; /// Dish description translated to target language final String? dishDescriptionTranslated; /// Nutri-Score known as the 5-Colour Nutrition label (A-E) final String? nutriScore; /// Reason for processing failure final String? failureReason; /// Serving size in metric units final num? servingSize; /// Metric unit (g for solids, ml for liquids) final NutritionalUnit? unit; /// Nutritional values as key-value pairs final Map? nutritionalFields; /// List of detected ingredients with nutritional information final List? ingredients; /// Upload timestamp in UTC final DateTime uploadedAt; /// Update timestamp in UTC final DateTime modifiedAt; /// The UTC time when food was consumed final DateTime? consumedAt; } ``` ### `NutritionRecordStatus` ```dart theme={null} enum NutritionRecordStatus { pending, processing, completed, failed, /// Unknown value was sent from API. SDK should be updated. unknown } ``` ### `NutritionalUnit` ```dart theme={null} enum NutritionalUnit { g, // grams mg, // milligrams mcg, // micrograms ml, // milliliters kcal, // kilocalories unknown } ``` ### `NutritionRecordIngredient` ```dart theme={null} class NutritionRecordIngredient { /// Ingredient name using LANGUAL standard terminology final String name; /// Ingredient name translated to target language final String? nameTranslated; /// Serving size in metric units final num servingSize; /// Metric unit (g for solids, ml for liquids) final NutritionalUnit unit; /// Nutritional values as key-value pairs final Map? nutritionalFields; } ``` ### `NutritionalField` Use this enum to specify which nutritional fields to include in the analysis: ```dart theme={null} 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 `Future` objects that can throw exceptions. Always wrap calls in try-catch blocks: ```dart theme={null} try { final record = await spikeConnection.analyzeNutrition( imageBase64: imageBase64, consumedAt: DateTime.now(), ); // Handle success } on SpikeException catch (e) { print('Spike error: ${e.message}'); } catch (e) { print('Unexpected error: $e'); } ``` ### 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: ```dart theme={null} // ❌ Don't request everything "just in case" final config = NutritionalAnalysisConfig( analysisMode: null, countryCode: null, languageCode: null, includeNutriScore: true, includeDishDescription: true, includeIngredients: true, includeNutritionFields: NutritionalField.values, // All 29 fields ); // ✅ Request only what you need final config = NutritionalAnalysisConfig( 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: ```dart theme={null} final record = await spikeConnection.getNutritionRecord(id: recordId); if (record == null) { // Handle not found return; } if (record.status != NutritionRecordStatus.completed) { if (record.status == NutritionRecordStatus.failed) { // Handle failure print('Failed: ${record.failureReason}'); } else { // Still processing print('Status: ${record.status}'); } return; } // Safe to access results print('Dish: ${record.dishName}'); ``` ### 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. Create Reusable Configuration If you're using the same settings across your app, create a shared configuration helper: ```dart theme={null} // nutrition_config.dart import 'package:spike_flutter_sdk/spike_flutter_sdk.dart'; class NutritionConfig { static final standard = NutritionalAnalysisConfig( analysisMode: NutritionRecordAnalysisMode.precise, countryCode: null, languageCode: null, includeNutriScore: true, includeDishDescription: null, includeIngredients: true, includeNutritionFields: [ NutritionalField.energyKcal, NutritionalField.proteinG, NutritionalField.fatTotalG, NutritionalField.carbohydrateG, NutritionalField.fiberTotalDietaryG, ], ); } // Usage final record = await spikeConnection.analyzeNutrition( imageBase64: imageBase64, consumedAt: DateTime.now(), config: NutritionConfig.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 # Flutter SDK Overview Source: https://docs.spikeapi.com/sdk-docs/flutter/overview Overview of SDK-based integrations with health platforms including common principles, integration creation process, and data extraction methods for mobile health data management. # Common principles SDK-based integrations (Apple Health Kit, Android Health Connect, and Samsung Health Data) have a few common principles that separate them from all other providers. * Data is stored only on the device (phone) therefore, requires to be actively extracted * Permission control is very gradual (each metric reading requires approval) * Data becomes available instantly as it's recorded * Integration requires active management through SDKs # Creating integrations Creating these integrations does not follow the OAuth path. Meaning * there will be no redirecting to the provider authorization page, * and therefore no callback to URL after permissions are granted. * Permissions are granted locally, on the phone itself, by triggering SDK methods. * Permissions are also managed by default OS schemas, application users navigating settings menus, are granted for application package (reinstalling the app might require reauthorization), can be revoked or ignored when requested by OS based on their policies. The schema below should explain the flow and explain the sequence of events, the moment integration gets created. # Data extraction Data stored only on the user's mobile device's local hardware (encrypted at rest). To make data available over API, first you must select and call SDK functions dedicated to sleep, workouts or other metrics reading. The schema below should explain the flow and explain the sequence of events, the moment when data becomes available for reading over API and SDK. # Events sequence schema SDK integration lifecycle # Flutter SDK Setup Source: https://docs.spikeapi.com/sdk-docs/flutter/setup This document provides a setup guide of the Spike SDK for Flutter platform. ## Version **Current Flutter SDK Version:** `4.7.14` ## Resources * Flutter Package: [Available here](https://pub.dev/packages/spike_flutter_sdk) * Example app: [Available here](https://gitlab.com/spike_api/public/spike-sdk-examples/-/tree/master/flutter-v3/flutter_spike_v3) ## Requirements * **iOS Version**: `13.0+` * **Xcode 26+** is required. * **Android Version**: `9.0+` (Level 28, P, Pie) ## Installation Add a dependency on Spike SDK in your pubspec.yaml file: ```yaml theme={null} dependencies: spike_flutter_sdk: ^4.3.34 ``` ## iOS Setup Guide Use `pod install` and `pod update` commands from `ios/` folder of your app to install/update `SpikeSDK`. ### iOS Signing & Capabilities To add HealthKit support to your application's Capabilities. * Open the `ios/` folder of your project in Xcode * Select the project name in the left sidebar * Open the Signing & Capabilities section * In the main view select '+ Capability' and double-click HealthKit More details you can find [here](https://developer.apple.com/documentation/healthkit/setting_up_healthkit). ### Info.plist Add Health Kit permissions descriptions to your `Info.plist` file: ```xml theme={null} NSHealthShareUsageDescription We will use your health information to better track workouts. NSHealthUpdateUsageDescription We will update your health information to better track workouts. ``` ## Android Setup Guide To add the SDK to your Android project, you have to add the following to your project's `build.gradle` file in the repositories block. ```gradle theme={null} allprojects { repositories { // Other repositories maven { url 'https://gitlab.com/api/v4/projects/43396247/packages/maven' } } } ``` Set Android SDK version in `local.properties` file: ```text theme={null} flutter.minSdkVersion=28 flutter.compileSdkVersion=34 ``` ### Android Permissions Include the necessary health permissions in your `AndroidManifest.xml` to fully leverage the Spike SDK and access data from apps integrated with Health Connect. Please refer to [this guide](https://developer.android.com/health-and-fitness/guides/health-connect/get-started#declare-permissions) for details on the required permissions. **Note**: Only request permissions essential to your app’s functionality. Requesting unused permissions will lead to app store rejection! ```xml theme={null} ``` Add an intent filter to your activity definition so that you can request the permissions at runtime. ```xml theme={null} ``` Check the contents of `android/app/src/main/kotlin/{YOUR_PACKAGE_ID}/MainActivity.kt`. You must see something like that in case it is the new app being developed: ```kotlin theme={null} import io.flutter.embedding.android.FlutterActivity class MainActivity: FlutterActivity() { } ``` You must change `FlutterActivity` to the `FlutterFragmentActivity` which means that your code should turn into the result similar to the one below: ```kotlin theme={null} import io.flutter.embedding.android.FlutterFragmentActivity class MainActivity: FlutterFragmentActivity() { } ``` In the Health Connect permissions activity, there is a link to your privacy policy. You need to grant the Health Connect app access to link back to your privacy policy. In the example below, you should either replace .MainActivity with an activity that presents the privacy policy or have the Main Activity route the user to the policy. This step may be required to pass Google app review when requesting access to sensitive permissions. ```xml theme={null} ``` **Note**: If permissions handling is not working, this might be related to launch mode being singleTop. This might be not needed, but some apps face problems when requesting permissions. If you face them, then you should try removing the property `android:launchMode="singleTop"`. **Note**: If the app is not building, it might be related to the label replacement issue. In this case, you should add `tools:replace="android:label"` to the application tag: ```xml theme={null} Please note that you need to call **requestHealthPermissions** each time users restart their app after making Spike Create Connection. ## Step 3: Get Data The maximum permitted query date range on Android Health Connect is 90 days There are four types of data you can retrieve from Spike: * **Statistics** are calculated values derived from records. * **Activities** are data about a user's activities or workouts. * **Sleep** is data about a user's sleep. * **Records** consist of the raw data points collected from user devices or applications. ### Statistics Now you can read hourly statistics data of steps and distance for today: ```dart theme={null} final now = DateTime.now(); final tomorrow = DateTime(now.year, now.month, now.day + 1); final weekAgo = now.subtract(const Duration(days: 7)); final statistics = await widget.connection.getStatistics( ofTypes: [StatisticsType.steps], from: weekAgo, to: tomorrow, interval: StatisticsInterval.day, ); ``` where: ```dart theme={null} enum StatisticsType { steps, distanceTotal, distanceWalking, distanceCycling, distanceRunning, caloriesBurnedTotal, caloriesBurnedBasal, caloriesBurnedActive; } enum StatisticsInterval { hour, day; } // Filter class StatisticsFilter { final bool excludeManual; final Set? providers; final Set? activityTags; } // Result: class Statistic { final DateTime start; final DateTime end; final int duration; final StatisticsType type; final num value; final Unit unit; final int? rowCount; final List? recordIds; } ``` ### Records ```dart theme={null} final now = DateTime.now(); final tomorrow = DateTime(now.year, now.month, now.day + 1); final weekAgo = now.subtract(const Duration(days: 7)); final records = await connection.getRecords( ofTypes: [MetricType.stepsTotal], from: weekAgo, to: tomorrow, filter: StatisticsFilter(excludeManual: false), ); ``` where: ```dart theme={null} enum MetricType { heartrateMax, heartrateAvg, heartrateMin, heartrate, heartrateResting, heartrateVariabilityRmssd, heartrateVariabilityRmssdDeepSleep, elevationMax, elevationAvg, elevationMin, elevationGain, elevationLoss, ascent, descent, caloriesBurnedActive, caloriesBurnedBasal, caloriesBurned, caloriesIntake, stepsTotal, floorsClimbed, distanceTotal, distanceWalking, distanceCycling, distanceRunning, distanceWheelchair, distanceSwimming, speedMax, speedAvg, speedMin, airTemperatureMax, airTemperatureAvg, airTemperatureMin, spo2Max, spo2Avg, spo2Min, longitude, latitude, elevation, durationActive, swimmingLengths, swimmingDistancePerStroke; } // Result: class Record { final String recordId; final String? inputMethod; final DateTime startAt; final DateTime? endAt; final DateTime modifiedAt; final int? duration; final String? provider; final String? providerSource; final bool? isSourceAggregated; final RecordSource? source; final Map? metrics; final List? activityTags; final String? activityType; final List? sessions; final List? laps; final List? segments; final List? splits; final List? samples; final List? routePoints; final List? sleep; } class ActivityEntry { final int? divisionRef; final String? divisionLabel; final DateTime? startAt; final DateTime? endAt; final int? duration; final Map? metrics; } ``` # Flutter SDK for Samsung Health Source: https://docs.spikeapi.com/sdk-docs/flutter/usage-guide-shd Start getting Spike data in 3 steps using Spike SDK for Samsung Health Data on Flutter platform. ## Requirements and Limitations Samsung Health Data is available on Android devices only! * Samsung Health Data SDK runs on devices with Android 10 (API level 29) or above. It is available on all Samsung smartphones and non-Samsung Android smartphones. * Samsung Health Data SDK works with Samsung Health. Samsung Health version 6.30.2 or higher is required. * Samsung Health Data SDK supports Java 17 or higher version. * The emulator is not supported. * Data obtained using Samsung Health Data SDK is for fitness and wellness information only. It is not for the diagnosis or treatment of any medical condition. ## Step 1: Create a Spike Connection If you already set up [Google Health Connect integration](/sdk-docs/flutter/usage-guide) in your app, you should skip to [this step](#step-2%3A-ask-user-for-permissions) and use the same Spike SDK connection object. To set up the Spike SDK create `SpikeConnectionV3` instance with your Spike application id, application user id and signature unique to each of your application users (more on generating signatures [here](/api-docs/authentication)): ```dart theme={null} import 'package:spike_flutter_sdk/spike_flutter_sdk.dart'; final spikeConnection = await SpikeSDKV3.createConnection( applicationId: 1000, signature: "signature", endUserId: "user-id", ); ``` ## Step 2: Ask User for Permissions If you want to read data from Samsung Health, you have to ensure the user gives your app permissions. First, you have to check if Samsung Health is available on users' phone using `checkSamsungHealthDataAvailability` method: ```dart theme={null} final availability = await spikeConnection.checkSamsungHealthDataAvailability() ``` where: ```dart theme={null} class SamsungHealthAvailability { final SamsungHealthAvailabilityStatus status; final int errorCode; final String message; } enum SamsungHealthAvailabilityStatus { // Samsung Health is not installed. Ask user to install it. notInstalled, // The version of Samsung Health is too old. Ask users to update it. updateRequired, // The Samsung Health Data is installed but is disabled. disabled, // Samsung Health has been installed but the user didn't perform an initial process, such as // agreeing to the Terms and Conditions. notInitialized, // Samsung Health returned other error. errorOther, // Samsung Health Data is available. installed, } ``` If Samsung Health is installed, you can ask user for permissions using `requestPermissions` method: ```dart theme={null} // Samsung Health integration has to be enabled in Spike SDK connection before // using further methods for reading data or managing permissions: spikeConnection.enableSamsungHealthDataIntegration() await spikeConnection.requestSamsungHealthDataPermissions( statisticTypes: [StatisticsType.steps], ); ``` Please note that users might only grant partial permissions. In such cases, it's up to you to decide whether your app can function effectively with limited access. The Spike SDK itself will still operate even without full permissions; however, it may result in no data being returned in certain scenarios. You can now use `StatisticsFilter(providers: {Provider.samsungHealthData})` to specifically retrieve data from Samsung Health. Alternatively, you can omit the providers parameter entirely and allow Spike to choose the most suitable data source based on your request. ## Step 3: Get Data Reading data is the same as in Android for [Google Health Connect](/sdk-docs/android/usage-guide#step-3%3A-get-data). The only difference is that you may want to filter results by `Provider.samsungHealthData` to get only Samsung Health data: ```dart theme={null} StatisticsFilter(providers: {Provider.samsungHealthData}) ``` ## Background Delivery Samsung Health data can be delivered in the background the same way as Apple HealthKit or Android Health Connect. If you want to use background delivery to get Samsung Health data, you have to enable Samsung Health Data integration first: ```dart theme={null} spikeConnection.enableSamsungHealthDataIntegration() ``` After enabling Samsung Health Data integration, you can use background delivery normally. See the [background delivery section](/sdk-docs/flutter/background-delivery) for more details. ## Developer Mode for Testing To test Samsung Health Data integration on your phone, you have to enable developer mode in the Samsung Health app: 1. Tap the ‘⋮’ button of Samsung Health in the top-right. 2. Select Settings > About Samsung Health. 3. Tap the version line region quickly 10 times or more. If you are successful, the Developer mode (Samsung Health Data SDK) button is displayed. 4. Select Developer mode (Samsung Health Data SDK) . 5. Agree with the Notice about usage of the Developer mode. 6. To read data from Samsung Health with Samsung Health Data SDK, turn Developer Mode for Data Read on. After your app is ready for release, you should apply for a Samsung Partnership Agreement. For more information, please contact Spike support. The Samsung Health developer mode is ONLY intended for testing or debugging your app. It is NOT for app users. Do not provide a developer mode guide to app users. ## See Also * [Samsung error codes](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3.samsung/-samsung-health-data-availability/error-code.html) * [SamsungHealthDataPermissionManager](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3.samsung/-samsung-health-data-permission-manager/index.html) * [SamsungHealthDataAvailabilityStatus](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3.samsung/-samsung-health-data-availability-status/index.html) * [SpikeConnectionV3.enableSamsungHealthDataIntegration](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3/-spike-connection-a-p-iv3/enable-samsung-health-data-integration.html) * [SpikeConnectionV3.disableSamsungHealthDataIntegration](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3/-spike-connection-a-p-iv3/disable-samsung-health-data-integration.html) * [SpikeConnectionV3.isSamsungHealthDataIntegrationEnabled](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3/-spike-connection-a-p-iv3/is-samsung-health-data-integration-enabled.html) # iOS SDK Backfill Source: https://docs.spikeapi.com/sdk-docs/ios/backfill Backfill historical data from Apple HealthKit. ## Backfill from Apple HealthKit Data is stored exclusively on the device (iPhone). Backfilling is possible from the moment the application user grants permissions and is limited by: * What data the user has on their device * What permissions your app has been granted (read access for specific data types) * User's health app settings or deletions * Your retention policy settings ## Manual Data Extraction Required Because Apple doesn't offer any other communication except HealthKit framework, you **must query needed data manually** to backfill. Before data is extracted, it won't be available to your backend over API calls. Enabling background data delivery also won't perform the backfill as it's designed for new data events. ## Implementation To implement backfill functionality, query historical data using the SDK's statistics methods for your desired date range: ```swift theme={null} import SpikeSDK // Example: Backfill last 7 days of steps data let calendar = Calendar.current let today = Date() for dayOffset in 0..<7 { guard let startDate = calendar.date(byAdding: .day, value: -dayOffset, to: today) else { continue } guard let endDate = calendar.date(byAdding: .day, value: 1, to: startDate) else { continue } do { let statistics = try await spikeConnection.getStatistics( ofTypes: [.steps], from: startDate, to: endDate, interval: .day, filter: StatisticsFilter(providers: [.apple]) ) // Process the statistics data for statistic in statistics { print("Steps for \(statistic.start): \(statistic.value)") } } catch { // Handle specific error types for better user experience if let hkError = error as? HKError { switch hkError.code { case .errorAuthorizationNotDetermined: print("Authorization not determined. Request permissions first.") case .errorAuthorizationDenied: print("Access denied. Guide user to Health app settings.") default: print("HealthKit error: \(hkError.localizedDescription)") } } else { print("Error fetching statistics for \(startDate): \(error.localizedDescription)") } } } ``` ## Best Practices ### Performance and User Experience * Keep the backfill process asynchronous for the best user experience * Segment requests into smaller date ranges to ensure optimal performance * Implement rate limiting to avoid overwhelming the HealthKit store * Consider implementing a progress indicator for longer backfill operations ### Privacy and Security * Handle permissions gracefully — users may grant partial access * Consider allowing users to control the backfill scope (date range, data types) * Comply with relevant regulations such as GDPR for user data protection ### Error Handling * Implement robust error handling for authorization failures * Gracefully handle scenarios where data is unavailable or incomplete * Manage data conflicts and duplicates effectively to maintain data integrity * Provide meaningful feedback to users about data access issues * Guide users to Health app settings when permissions are denied ### Data Management * Avoid storing HealthKit data outside the HealthKit store unless necessary * If external storage is required, ensure data is encrypted and secure * Validate data before processing to ensure accuracy and consistency * Respect a user's ability to revoke permissions at any time # iOS SDK Background Delivery Source: https://docs.spikeapi.com/sdk-docs/ios/background-delivery Background delivery ensures that data updates are sent to your backend via webhooks, even when the application is in the background or closed. ### Important Notes About Background Delivery on iOS * For most data types, the most possible frequency of updates is 1 hour. * iOS can update data more frequently for some data types, for example, vo2 max. * iOS may throttle the frequency of updates for background delivery depending on the app's activity, battery state, etc. * Background delivery is not possible while a device is locked, so it will be executed only when the device is unlocked. * iOS may stop background delivery if it detects that the app is not active for a long time. * The feature is available starting with iOS 15. **Important:** The Spike SDK, along with any other HealthKit applications, cannot guarantee data synchronization on a fixed schedule. The hourly sync interval serves as a guideline rather than a strict requirement enforced by iOS. Consequently, the actual synchronization frequency may vary, occurring hourly, once per day, or during specific system-defined events, such as the conclusion of Sleep Mode or when the device begins charging. ## Setup ### Enable Background Delivery for the Application Target * Open the folder of your project in Xcode * Select the project name in the left sidebar * Open the Signing & Capabilities section * Select HealthKit background delivery under the HealthKit section ### Initialization at Application Startup Add Spike initialization code to your AppDelegate inside application:didFinishLaunchingWithOptions: method: ```swift theme={null} import SpikeSDK ... func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { ... Spike.configure() ... } ``` For SwiftUI-based apps follow few steps: Create a custom class that inherits from NSObject and conforms to the UIApplicationDelegate protocol: ```swift theme={null} import SpikeSDK class AppDelegate: NSObject, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { Spike.configure() return true } } ``` And now in your App struct, use the UIApplicationDelegateAdaptor property wrapper to tell SwiftUI it should use your AppDelegate class for the application delegate: ```swift theme={null} .@main struct YourApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate ... } ``` ### Ask Spike SDK for Background Delivery To enable background delivery, you need to call the enableBackgroundDelivery method: ```swift theme={null} do { try await spikeConnection.enableBackgroundDelivery( forStatisticsTypes: [.steps] ) } catch { print("\(error)") } ``` Calling enableBackgroundDelivery will overwrite all the previous settings, so you have to call it with all the data types that you want in one call. It accepts all the possible data types that can be delivered in the background (statistics, records, activities and sleep). ```swift theme={null} func enableBackgroundDelivery( forStatistics: [StatisticsType]?, forMetrics: [MetricType]?, forActivities: [ActivityConfig]?, forSleep: [SleepConfig]? ) async throws or public struct BackgroundDeliveryConfig: Codable, Hashable, Sendable { public var statisticsTypes: [StatisticsType]? public var metricsTypes: [MetricType]? public var activityConfigs: [ActivityConfig]? public var sleepConfigs: [SleepConfig]? } ``` You can also call `getBackgroundDeliveryConfig()` to get the current configuration and `disableBackgroundDelivery()` to disable background delivery. # iOS SDK Changelog Source: https://docs.spikeapi.com/sdk-docs/ios/changelog A changelog documenting version updates and changes of Spike SDK for iOS platform. # Spike iOS SDK Changelog ## 4.7.11 * Removed the deprecated v1/v2 connection API ## 4.6.11 * Added HealthKit-backed nutrition support to the v3 HealthKit flow. You can now request dietary permissions, backfill historical nutrition data, and include nutrition fields in background delivery by passing `forNutrition` / `nutritionalFields`. ``` try await spikeConnection.requestPermissionsFromHealthKit( forNutrition: [.energyKcal, .proteinG, .carbohydrateG, .fatTotalG] ) try await spikeConnection.backfill( forNutrition: [.energyKcal, .proteinG, .carbohydrateG, .fatTotalG], days: 7 ) ``` * `getNutritionRecords(from:to:)` now reads requested HealthKit nutrition data before returning nutrition records when HealthKit integration is enabled. ## 4.5.21 * Added `backfill` on the v3 connection to upload historical HealthKit data for a chosen number of days. Call it after `requestPermissionsFromHealthKit(forStatistics:forMetrics:forActivities:forSleep:)` and pass the statistics, metrics, activities, and sleep configurations you want to fill in; only the categories you provide are pushed. ``` try await spikeConnection.backfill( forStatistics: [.steps], forMetrics: nil, forActivities: nil, forSleep: nil ) ``` * Added `requestPermissionsFromHealthKitAndBackfill` — a convenience method that requests HealthKit permissions and immediately starts backfilling historical data. Today and yesterday are pushed before the method returns so you can display data right away; older days continue uploading in the background. ``` try await spikeConnection.requestPermissionsFromHealthKitAndBackfill( forStatistics: [.steps], forMetrics: nil, forActivities: nil, forSleep: nil, backfillDays: 7 ) ``` * Fixed concurrency issue ## 4.5.11 * Improved handling of deleted integrations — background delivery is now automatically disabled when the integration no longer exists * Improved error handling for API responses * Added `RecordConfig` for configuring records queries: * `includeSamples` - whether to include samples (raw data points) in the response * Updated `getRecords` to accept an optional `RecordConfig` parameter: ``` spikeConnection.getRecords( ofTypes: metricTypes, from: startDate, to: endDate, filter: StatisticsFilter(excludeManual: true), configured: RecordConfig(includeSamples: true) ) ``` ## 4.4.11 * Updated `Unit` enum with new values: * Added: `miles`, `mPerSec`, `fahrenheit`, `ms`, `seconds`, `degrees`, `sleepStage`, `secPerM`, `rpm`, `spm`, `breathsPerMin`, `g`, `lbs`, `st`, `mmHg`, `mLPerKgPerMin`, `uV`, `mgPerDl`, `w` * Removed: `kmh`, `kmPerMin` * Improved data communication with API * Enhanced the `getSleep` endpoint. Important: * Dates provided are now interpreted as dates only; the time component is ignored when querying the API. * The primary night sleep period is associated with the calendar date on which it ends. ## 4.3.161 * Better Nutrition Facts Label Recognition response parsing ## 4.3.151 * Improved data push to API * Fixed workout digest generation for caching * Improved client cache configuration ## 4.3.141 * Added Nutrition Facts Label Recognition for extracting nutritional information from label photos. For more information please visit our [documentation](https://docs.spikeapi.com). * `recognizeNutritionFactsLabel(imageBase64:config:)` - Analyze a nutrition facts label image and extract nutritional data * `recognizeNutritionFactsLabel(image:config:)` - Convenience method that accepts UIImage * Added methods for manual nutrition record management: * `createNutritionRecord(_:)` - Insert a new nutrition record with custom data * `replaceNutritionRecord(_:)` - Replace an existing nutrition record completely * Added `NutritionRecord` extension for creating and manipulating nutrition records: * `NutritionRecord.init(...)` - Create a new nutrition record with specified properties * `NutritionRecord.add(ingredient:)` - Add an ingredient to a record * `NutritionRecord.remove(ingredient:)` - Remove an ingredient from a record * `NutritionRecord.update(servingSize:unit:)` - Update serving size and unit * Added `NutritionRecordIngredient` extension for creating and manipulating ingredients: * `NutritionRecordIngredient.init(...)` - Create a new nutrition record ingredient * `NutritionRecordIngredient.set(nutritionalField:value:)` - Set a nutritional field value * `NutritionRecordIngredient.remove(nutritionalField:)` - Remove a nutritional field * `NutritionRecordIngredient.update(servingSize:)` - Scale serving size and nutritional values proportionally * Nutrition AI methods now return `NutritionRecordAnalysisResult` instead of `NutritionRecord`: * `NutritionRecordAnalysisResult` wraps the nutrition record with additional metadata including `status`, `recordId`, `uploadedAt`, and `failureReason` * Access the nutrition data via the `nutritionRecord` property * Affected methods: `analyzeNutrition`, `getNutritionRecords`, `getNutritionRecord`, `updateNutritionRecordServingSize`, `createNutritionRecord`, `replaceNutritionRecord` * Added new `NutritionRecordStatus` case: `.updated` ## 4.3.131 * Added Nutrition AI features for analyzing food images. For more information please visit our [documentation](https://docs.spikeapi.com). * `analyzeNutrition(imageBase64:consumedAt:config:)` - Analyze food image synchronously and wait for results * `analyzeNutrition(image:consumedAt:config:)` - Convenience method that accepts UIImage * `submitNutritionForAnalysis(imageBase64:consumedAt:config:)` - Analyze food image asynchronously, returns record ID * `submitNutritionForAnalysis(image:consumedAt:config:)` - Convenience method that accepts UIImage * `getNutritionRecords(from:to:)` - Retrieve nutrition records for a date range * `getNutritionRecord(id:)` - Get a specific nutrition record by ID * `deleteNutritionRecord(id:)` - Delete a nutrition record * `updateNutritionRecordServingSize(id:servingSize:)` - Update serving size for a nutrition record * Added new `Provider`: * luna * Added new `ProviderSource` types: * fitbitSkinTemperatureSummary * garminWellnessSkinTemperatureSummary * lunaSleep * Removed `ProviderSource` types: * fitbitUserActivitiesList * fitbitUserActivitiesDate * fitbitUserActivitiesHeartDate * fitbitUserActivitiesHeartDateInterday * withingsMeasureGetWorkouts * withingsMeasureGetActivity * withingsMeasureGetMeas * Removed `ActivityTag`: * onBicycle * Removed `Provider`: * wahoo ## 4.3.121 * Implemented enabling HealthKit integration in the admin console right after requesting permissions ## 4.3.111 * Removed `providerUserIdentifier` from `IntegrationInitConfig` * Added ability to read mindfulness activities: ``` spikeConnection.getActivities( configured: ActivityConfig(activityCategories: [.mindfulness]) from: dateFrom, to: dateTo ) ``` * Added new `ActivityTag` types: * mindfulness * Added new `ActivityType` types: * ecgMeasurement * triathlon * biathlon * duathlon * rollerblading * skateboarding * skating * calisthenics * weightLifting * canoeing * floorball * jiuJitsu * diving * orienteering * bootcamp * motorsports * horseriding * paragliding * multisport * bloodTest * mindfulnessSession * Added new `ProviderSource` types: * appleHealthkitMindfulness * healthConnectMindfulnessSession * Added new `ActivityCategory` type: * mindfulness * Added new `MetricType`: * heartrateRestingMin * heartrateRestingMax * cadence * cadenceMin * cadenceMax * pace * airTemperature * bodyTemperatureMax * bodyTemperatureMin * basalBodyTemperature * basalBodyTemperatureMax * basalBodyTemperatureMin * skinTemperatureMax * skinTemperatureMin * sleepSkinTemperatureDeviation * Added new `StatisticsType`: * sleepSkinTemperatureDeviation * hrvRmssd * hrvSdnn * Fixed typo in metric type: `swimmingLengths` (was incorrectly spelled as `swimming_lenghts`) ## 4.3.101 * Improved how sleep data is read for sleep score ## 4.3.91 * Added `.coros` `Provider` * Updated `ProviderSource` enum * Improved the way data is sent to API to reduce the size and speed of calls to Spike * Added new Statistic types: * stressScore * recoveryScore * activityScore * Added new Metric types for `getSleep` request: * sleepDuration * sleepDurationAwake * sleepDurationDeep * sleepDurationLight * sleepDurationNap * sleepDurationRem * sleepEfficiency * sleepInterruptions * sleepLatency * sleepScore * Better keychain compatibility NOTE: If you use the keychain in your app with a service name equal to your bundle identifier, please check if it contains key named `spikeApiToken` and delete it. ## 4.3.81 * Added `MetricType`: `.glucose` ## 4.3.71 * The old `SpikeSDK` API has been deprecated * Added `disableHealthKitIntegration` and `isHealthKitIntegrationEnabled` functions in spike connection ## 4.3.61 * Added .distanceSwimming into the list of metrics available in activities * Added new providers: .dexcom, .freestyleLibre, .huawei, .strava ## 4.3.51 * Added new `Provider`: * samsungHealthData * Added new `ProviderSource`: * samsungHealthDataAggregation * Renamed `MetricTypes`: * `sleepBreathingRate` to `breathingRate` * `sleepBreathingRateMin` to `breathingRateMin` * `sleepBreathingRateMax` to `breathingRateMax` ## 4.3.41 * IntegrationInitConfig is now codable * Additional check in `getIntegrationInitUrl` for email in ultrahuman integration ## 4.3.31 * Added new metric types: * bodyFat * bodyFatMax * bodyFatMin * bodyBoneMass * bodyMassIndex * bloodPressureSystolic * bloodPressureSystolicMax * bloodPressureSystolicMin * bloodPressureDiastolic * bloodPressureDiastolicMax * bloodPressureDiastolicMin * Added new fields in `UserProperties`: * bodyBoneMass * bodyFat * bodyMassIndex * Updated `getIntegrationInitUrl`. Now it accepts `IntegrationInitConfig` config object where you can pass: * `redirectUri`: will override the one set in admin console * `state`: when the authorization server redirects back to the client, it includes the `state` value originally sent * `providerUserIdentifier`: at the moment used (and required) only when integrating with ultrahuman * New provider: `ultrahuman` When integrating with Ultrahuman, you have to provide ultrahuman user email in `IntegrationInitConfig.providerUserIdentifier`. Example: ``` spikeConnection.getIntegrationInitUrl(provider: .ultrahuman, config: IntegrationInitConfig(providerUserIdentifier: "user@mail.com")) ``` * Improved transport protocol for background delivery ## 4.3.21 * Improved how statistics are read from HealthKit ## 4.3.11 * Improved transport protocol for even faster requests to Spike API * Added new statistic metrics: heartrate, heartrateMax, heartrateMin, heartrateResting ## 4.2.31 * Added sleepScore property in the Record type * Added new metrics: * spo2 * bodyTemperature * skinTemperature (available only on getSleep()) * sleepBreathingRate (available only on getSleep()) * sleepBreathingRateMin (available only on getSleep()) * sleepBreathingRateMax (available only on getSleep()) * sleepBreathingRateAvg (available only on getSleep()) ## 4.2.21 * New `getUserProperties` method for reading: weight, height, timezone, birthdate, gender * New metric: VO2max (Cardio Fitness) * New statistic: sleep score ## 4.2.11 * 🔴 **Breaking Change**: Update to `Spike.createConnectionAPIv3`. The method `Spike.createConnectionAPIv3(appId:, authToken:, customerEndUserId:)` has been updated to: **`Spike.createConnectionAPIv3(applicationId:, signature:, endUserId:)`** * The `applicationId` parameter must now be provided as an **`Int`**. * The `signature` parameter now requires an **`HMAC-SHA256` signed user ID**. * ⚠️ **Security Notice:** * **Do not store your HMAC signing key within the application itself**, as this poses a security risk. * Instead, generate and provide the signature from your backend. * **Legacy Support**: For **development purposes only**, the previous connection flow remains available under the renamed method: `Spike.createConnectionAPIv3_legacy(appId:, authToken:, customerEndUserId:)` ## 4.1.11 * Added background delivery * Added log callback * Added more sleep data to be sent ## 4.0.11 Completely new SDK! Please see our official documentation for more details and usage instructions. ## 2.4.5 * Fixed the cache issue in the release process ## 2.4.4 * Reverted version 2.4.3 * Implemented reading more distance fields in a workout object from HealthKit ## 2.4.3 * Added third party integration initialization. * Implemented reading more distance fields in a workout object from HealthKit ## 2.4.2 * Added customer user id validation. ## 2.4.1 * Fixed the timezone in case it changes while the app is running ## 2.4.0 * Implemented a better way to authenticate with API * Added ECG ## 2.3.2 * Added trigger property to `extractAndPostData` request ## 2.3.1 * Added cycling parameters to Activities stream data request ## 2.3.0 * Removed environment configuration ## 2.2.3 * Added prop `provider_timestamp` to an Activities summary model ## 2.2.2 * Included intraday entries of a Steps intraday data type for range requests ## 2.2.1 * Added handling Steps an intraday entries metadata object ## 2.2.0 * Aligned SpikeData wrapper model with the server ## 2.1.14 * Fixed date range parsing ## 2.1.13 * Step intraday data improvements ## 2.1.12 * Step intraday data improvements ## 2.1.11 * Expose isHealthDataAvailable method ## 2.1.10 * Use iso8601 standardized Calendar # iOS SDK Logging Source: https://docs.spikeapi.com/sdk-docs/ios/logging Getting logs for troubleshooting and debugging from Spike SDK for iOS platform. ## Code Examples To receive logs from Spike SDK, use the following code: ```swift theme={null} Spike.setLogCallback { level, message in print("\(message)") } ``` If you are using background delivery, ensure this code runs in your application's `didFinishLaunchingWithOptions` method before you start using Spike SDK: ```swift theme={null} func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { ... Spike.setLogCallback { level, message in print("\(message)") } Spike.configure() ... } ``` # iOS SDK Nutrition AI Source: https://docs.spikeapi.com/sdk-docs/ios/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. All Spike SDK async method calls should be wrapped in a `do-catch` block. See [Error Handling](#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 * **`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 ) ``` **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. ```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.") } } ``` 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. ## 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 # iOS SDK Overview Source: https://docs.spikeapi.com/sdk-docs/ios/overview Overview of SDK-based integrations with health platforms including common principles, integration creation process, and data extraction methods for mobile health data management. # Common principles SDK-based integrations (Apple Health Kit, Android Health Connect, and Samsung Health Data) have a few common principles that separate them from all other providers. * Data is stored only on the device (phone) therefore, requires to be actively extracted * Permission control is very gradual (each metric reading requires approval) * Data becomes available instantly as it's recorded * Integration requires active management through SDKs # Creating integrations Creating these integrations does not follow the OAuth path. Meaning * there will be no redirecting to the provider authorization page, * and therefore no callback to URL after permissions are granted. * Permissions are granted locally, on the phone itself, by triggering SDK methods. * Permissions are also managed by default OS schemas, application users navigating settings menus, are granted for application package (reinstalling the app might require reauthorization), can be revoked or ignored when requested by OS based on their policies. The schema below should explain the flow and explain the sequence of events, the moment integration gets created. # Data extraction Data stored only on the user's mobile device's local hardware (encrypted at rest). To make data available over API, first you must select and call SDK functions dedicated to sleep, workouts or other metrics reading. The schema below should explain the flow and explain the sequence of events, the moment when data becomes available for reading over API and SDK. # Events sequence schema SDK integration lifecycle # iOS SDK Setup Source: https://docs.spikeapi.com/sdk-docs/ios/setup This document provides a setup guide of the Spike SDK for iOS platform. ## Version **Current Swift SDK Version:** `4.7.11` ## Resources * Swift Package: [Available here](https://gitlab.com/spike_api/spike-ios-sdk) * API Reference for `SpikeSDK`: [Available here](https://spike_api.gitlab.io/spike-ios-sdk/documentation/spikesdk/spikeconnectionapiv3/) * Example app: [Available here](https://gitlab.com/spike_api/public/spike-sdk-examples/-/tree/master/swift-v3) ## Requirements * **iOS Version**: `13.0+` * **Xcode 26+** is required. ## iOS Signing & Capabilities To add HealthKit support to your application's Capabilities: 1. Open the `iOS/` folder of your project in Xcode. 2. Select the project name in the left sidebar. 3. Open the **Signing & Capabilities** section. 4. In the main view, select **+ Capability** and double-click **HealthKit**. For more details, see [Apple's HealthKit setup guide](https://developer.apple.com/documentation/healthkit/setting_up_healthkit). ## Info.plist Add Health Kit permissions descriptions to your `Info.plist` file: ```xml theme={null} NSHealthShareUsageDescription We will use your health information to better track workouts. NSHealthUpdateUsageDescription We will update your health information to better track workouts. ``` ## SDK Installation ### CocoaPods CocoaPods is a dependency manager for Cocoa projects. To integrate `SpikeSDK` into your Xcode project using CocoaPods, specify it in your `Podfile`: ``` pod 'SpikeSDK' ``` Use `pod install` and `pod update` commands to install/update pods afterward. ### Swift Package Manager To integrate `SpikeSDK` into your Xcode project using Swift Package Manager, add it in your `Package.swift` or through the Project's **Package Dependencies** tab: ```swift theme={null} dependencies: [ .package(url: "https://gitlab.com/spike_api/spike-ios-sdk", .upToNextMinor(from: "4.7.11")) ] ``` # iOS SDK Usage Guide Source: https://docs.spikeapi.com/sdk-docs/ios/usage-guide Start getting Spike data in 3 steps using Spike SDK for iOS platform. All Spike SDK async method calls should be wrapped into try catch block. ## Step 1: Create a Spike Connection To set up the Spike SDK create `SpikeConnectionV3` instance with your Spike application id, application user id and signature unique to each of your application users (more on generating signatures [here](/api-docs/authentication)): ```swift theme={null} import SpikeSDK static func createConnectionAPIv3( applicationId: Int, signature: String, endUserId: String ) async throws -> SpikeConnectionAPIv3 ) ``` ## Step 2: Ask User for Permissions Provide permissions to access iOS HealthKit data. Spike SDK method will check required permissions and request them if needed. Permission dialog may not be shown according to iOS permissions rules. ```swift theme={null} try await spikeConnection.requestPermissions(forStatistics: [ .stepsTotal, .distanceWalking, ] ) ``` ## Step 3: Get Data Info: The maximum permitted date range is 90 days There are four types of data you can retrieve from Spike: * **Statistics** are calculated values derived from records. * **Activities** are data about a user's activities or workouts. * **Sleep** is data about a user's sleep. * **Records** consist of the raw data points collected from user devices or applications. #### Statistics Get daily statistics for steps and total distance from Apple Health: ```swift theme={null} func getStatistics( ofTypes types: [StatisticsType.steps], from: Date.now.addingTimeInterval(-60 * 60 * 24), to: Date.now, interval: StatisticsInterval.hour, filter: StatisticsFilter(providers: [.apple]) ) async throws -> [Statistic] ``` where: ```swift theme={null} public enum StatisticsType: String { case steps case distanceTotal = "distance_total" case distanceWalking = "distance_walking" case distanceCycling = "distance_cycling" case distanceRunning = "distance_running" case caloriesBurnedTotal = "calories_burned_total" case caloriesBurnedBasal = "calories_burned_basal" case caloriesBurnedActive = "calories_burned_active" } public enum StatisticsInterval: String, Codable { case hour case day } public struct StatisticsFilter { public var excludeManual: Bool = false public var providers: [Provider]? = nil } // Result: public struct Statistic: Codable, Hashable { public var start: Date public var end: Date public var duration: Int public var type: StatisticsType public var value: Double public var unit: Unit public var rowCount: Int? public var recordIds: [UUID]? } ``` #### Records ```swift theme={null} func getRecords( ofTypes types: [MetricType.stepsTotal], from: Date.now.addingTimeInterval(-60 * 60 * 24), to: Date.now, filter: StatisticsFilter(providers: [.apple]) ) async throws -> [Record] ``` where: ```swift theme={null} public enum MetricType: String, Codable, Hashable { case stepsTotal case distanceTotal case distanceWalking case distanceCycling case distanceRunning case caloriesBurnedActive case caloriesBurnedBasal case caloriesBurnedTotal } // Result public struct Record: Codable, Hashable { public var recordId: UUID public var inputMethod: InputMethod? public var startAt: Date public var endAt: Date? public var modifiedAt: Date public var duration: Int? public var provider: Provider? public var providerSource: ProviderSource? public var isSourceAggregated: Bool? public var source: RecordSource? public var metrics: [String: Double]? public var activityTags: [ActivityTag]? public var activityType: ActivityType? public var sessions: [ActivityEntry]? public var laps: [ActivityEntry]? public var segments: [ActivityEntry]? public var splits: [ActivityEntry]? public var samples: [ActivitySamples]? public var routePoints: [ActivitySamples]? } ``` # React Native SDK Background Delivery Source: https://docs.spikeapi.com/sdk-docs/react-native/background-delivery Background delivery ensures that data updates are sent to your backend via webhooks, even when the application is in the background or closed. ## iOS ### Important Notes About Background Delivery on iOS * For most data types, the most possible frequency of updates is 1 hour. * iOS can update data more frequently for some data types, for example, vo2 max. * iOS may throttle the frequency of updates for background delivery depending on the app's activity, battery state, etc. * Background delivery is not possible while a device is locked, so it will be executed only when the device is unlocked. * iOS may stop background delivery if it detects that the app is not active for a long time. * The feature is available starting with iOS 15. **Important:** The Spike SDK, along with any other HealthKit applications, cannot guarantee data synchronization on a fixed schedule. The hourly sync interval serves as a guideline rather than a strict requirement enforced by iOS. Consequently, the actual synchronization frequency may vary, occurring hourly, once per day, or during specific system-defined events, such as the conclusion of Sleep Mode or when the device begins charging. ### Setup #### Enable Background Delivery for the Application Target * Open XCode with your ios project * Open the folder of your project in Xcode * Select the project name in the left sidebar * Open the Signing & Capabilities section * Select HealthKit background delivery under the HealthKit section #### Initialization at Application Startup You can skip this step if you are using Expo. For background delivery to work properly, you need to initialize the Spike SDK at app startup. `AppDelegate.swift`: ```swift theme={null} import SpikeSDK ... override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { ... Spike.configure() ... } ... ``` or `AppDelegate.mm`: ```text theme={null} #import ... - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ... [Spike configure]; ... } ``` ## Android ### Important Notes About Background Delivery on Android * Background delivery is scheduled to run every hour, but ultimately, Android decides when the delivery will be executed. * Android may throttle the frequency of updates for background delivery depending on the app's activity, battery state, etc. * Android may stop background delivery if it detects that the app is not active for a long time. * There is a limit of queries that can be done in Health Connect, and it is different for foreground and background reads, so please request only essential data to be delivered in the background. More information in Health Connect documentation. **Important**: The Spike SDK, along with any other applications, cannot guarantee data synchronization on a fixed schedule. The hourly sync interval serves as a guideline rather than a strict requirement enforced by Android. Consequently, the actual synchronization frequency may vary, occurring hourly, once per day, or during specific system-defined events, such as the conclusion of Sleep Mode or when the device begins charging. ### Setup Add the following permission to your AndroidManifest.xml: ```xml theme={null} ``` ## React Native Specific Add `includeBackgroundDelivery: true` when asking for permissions: ```javascript theme={null} spikeConnection.requestHealthPermissions({ statisticTypes: statisticTypes, includeBackgroundDelivery: true, }) ``` You can also ask for background delivery permission at the same time as other permissions. Now you can enable background delivery: ```javascript theme={null} try { await spikeConnection.enableBackgroundDelivery({ statisticTypes: [StatisticsType.steps, StatisticsType.distanceTotal], sleepConfigs: [new SleepConfig({ includeMetricTypes: [], })], }) toast.show("Background delivery enabled") } catch (error) { console.log(`${error}`); toast.show(`${error}`); } catch (error) { console.log(`${error}`); } ``` Keep in mind that calling `enableBackgroundDelivery()` will overwrite previous configuration. So you have to call it with all the data types you want in one call. To check current configuration call `getBackgroundDeliveryConfig()` method. To stop background delivery call `disableBackgroundDelivery()` method. ## Samsung Health If Samsung Health Data is enabled before enabling background delivery, `enableBackgroundDelivery` will automatically enable Samsung Health Data background delivery. # React Native SDK Changelog Source: https://docs.spikeapi.com/sdk-docs/react-native/changelog A changelog documenting version updates and changes of Spike SDK for React Native platform. If after updating you get errors while building the iOS app, please do the following: 1\. 2. - delete old pods 3\. - install updated pods 4\. Open project in XCode and do a full clean: or # Spike ReactNative SDK Changelog ## 4.7.13 * Removed the deprecated v1/v2 connection API * Updated Android SDK to `4.7.12` * Updated iOS SDK to `4.7.11` ## 4.6.13 * Updated Android SDK to `4.6.12` * Updated iOS SDK to `4.6.11` * Added nutrition support across HealthKit (iOS), Health Connect (Android), and Samsung Health Data (Android). You can now request nutrition permissions, backfill historical nutrition data, and include nutrition fields in background delivery by passing `nutritionalFields` to `requestHealthPermissions`, `backfill`, `enableBackgroundDelivery`, and `requestPermissionsFromHealthKitAndBackfill`. ``` await connection.requestHealthPermissions({ nutritionalFields: [ NutritionalField.energyKcal, NutritionalField.proteinG, NutritionalField.carbohydrateG, NutritionalField.fatTotalG, ], }); await connection.backfill({ nutritionalFields: [ NutritionalField.energyKcal, NutritionalField.proteinG, NutritionalField.carbohydrateG, NutritionalField.fatTotalG, ], days: 7, }); await connection.enableBackgroundDelivery({ nutritionalFields: [ NutritionalField.energyKcal, NutritionalField.proteinG, NutritionalField.carbohydrateG, NutritionalField.fatTotalG, ], }); ``` * `getNutritionRecords({ from, to })` now reads the latest local HealthKit / Health Connect / Samsung Health Data nutrition data before returning records when the corresponding integration is enabled. * `BackgroundDeliveryConfig` now exposes the `nutritionalFields` currently registered for background delivery. * Added `close()` on the v3 connection to disconnect from the SDK, release the native connection, and stop background delivery when it is enabled. ``` await connection.close(); ``` * IMPORTANT! If after updating you get errors while building iOS app please do following: 1. `rm -rf ios/Pods/ ios/Podfile.lock` - delete old pods 2. `cd ios; pod install --repo-update` - install updated pods 3. Open project in XCode 26 and do a full clean: `Product -> Clean build folder` or `Shift + Command + K` ## 4.5.23 * Updated Android SDK to `4.5.22` * Updated iOS SDK to `4.5.21` * Added **backfill** on the v3 connection to upload historical health data for a chosen number of days. Call it after you have granted the relevant permissions (HealthKit on iOS; Health Connect and/or Samsung Health Data on Android). ``` await connection.backfill({ statisticTypes, metricTypes, activityConfigs: [activityConfig], sleepConfigs: [sleepConfig], }); ``` * **iOS (HealthKit):** added `requestPermissionsFromHealthKitAndBackfill` — requests read permissions for the given categories, then starts a backfill. Data for today and yesterday is pushed before the call resolves; older days continue in the background. ``` await connection.requestPermissionsFromHealthKitAndBackfill({ statisticTypes, metricTypes, activityConfigs: [activityConfig], sleepConfigs: [sleepConfig], }); ``` * **Android (Samsung Health Data):** added `requestPermissionsFromSamsungHealthDataAndBackfill` — enables Samsung Health Data integration, requests permissions for the given categories, then starts a backfill. * **Android (Health Connect):** added `requestPermissionsFromHealthConnectAndBackfill` — enables Health Connect integration, requests permissions for the given categories, then starts a backfill. * **iOS:** If the app fails to build after upgrading, remove `ios/Pods` and `ios/Podfile.lock`, then run `pod install --repo-update` in `ios`, and run clean from Xcode. ## 4.5.13 * Updated Android SDK to `4.5.12` * Updated iOS SDK to `4.5.11` * Added `RecordConfig` for configuring records queries: * `includeSamples` - whether to include samples (raw data points) in the response * Updated `getRecords` to accept an optional `RecordConfig` parameter: ``` connection.getRecords({ ofTypes: metricTypes, from: startDate, to: endDate, filter: new StatisticsFilter({ excludeManual: true }), config: new RecordConfig({ includeSamples: true }) }) ``` * IMPORTANT! If after updating you get errors while building iOS app please do following: 1. `rm -rf ios/Pods/ ios/Podfile.lock` - delete old pods 2. `cd ios; pod install --repo-update` - install updated pods 3. Open project in XCode 26 and do a full clean: `Product -> Clean build folder` or `Shift + Command + K` ## 4.4.13 * Updated Android SDK to `4.4.12` * Updated iOS SDK to `4.4.11` * Updated `Unit` enum with new values: * Added: `miles`, `mPerSec`, `fahrenheit`, `ms`, `seconds`, `degrees`, `sleepStage`, `secPerM`, `rpm`, `spm`, `breathsPerMin`, `g`, `lbs`, `st`, `mmHg`, `mLPerKgPerMin`, `uV`, `mgPerDl`, `w` * Removed: `kmh`, `kmPerMin` * Improved data communication with API * Enhanced the `getSleep` endpoint. Important: * Dates provided are now interpreted as dates only; the time component is ignored when querying the API. * The primary night sleep period is associated with the calendar date on which it ends. ## 4.3.233 * Fixed nutrition record data so that `nutritionalFields` keys in ingredients and records match the `NutritionalField` enum values. You can now look up values using `ingredient.nutritionalFields[NutritionalField.energyKcal]` and similar expressions as intended. * Updated Android SDK to `4.3.142` * Internal improvements * Updated iOS SDK to `4.3.161` * Better Nutrition Facts Label Recognition response parsing ## 4.3.223 * Nutrition record and ingredient helpers are now methods on utils objects instead of standalone exports. * Use `NutritionRecordUtils.withIngredient(record, ingredient)` instead of `withIngredient(record, ingredient)` * Use `NutritionRecordUtils.withoutIngredient(record, ingredient)` instead of `withoutIngredient(record, ingredient)` * Use `NutritionRecordUtils.withServingSize(record, servingSize, unit)` instead of `withServingSize(record, servingSize, unit)` * Use `NutritionRecordIngredientUtils.withNutritionalField(ingredient, field, value)` instead of `withNutritionalField(ingredient, field, value)` * Use `NutritionRecordIngredientUtils.withoutNutritionalField(ingredient, field)` instead of `withoutNutritionalField(ingredient, field)` * Use `NutritionRecordIngredientUtils.withScaledServingSize(ingredient, newServingSize)` instead of `withScaledServingSize(ingredient, newServingSize)` ## 4.3.213 * MetricType: added namespace with metric allowlists for API requests: * `MetricType.availableForRecordsRequest` – metrics accepted by records API request * `MetricType.availableForActivitiesRequest` – metrics accepted by activities API request * `MetricType.availableForSleepRequest` – metrics accepted by sleep API request * `MetricType.availableForPersonRequest` – metrics accepted by person API request * Update iOS SDK to `4.3.151`: * Improved data push to API * Fixed workout digest generation for caching * Improved client cache configuration ## 4.3.203 * Updated Android SDK to `4.3.132`: * Samsung Health Data Integration - Major Expansion: * Added support for reading and pushing the following data types from Samsung Health: * Workouts (exercises) * Sleep data * Body composition data (weight, height, BMI, body fat) * User profile data (weight, height, birth date, gender) * Blood pressure measurements * Skin temperature measurements * Heart rate measurements * Blood oxygen saturation (SpO2) measurements * Body temperature measurements * More efficient Samsung Health Data pushes * Large Samsung Health Data pushes are now automatically split into smaller chunks for reliable delivery * Large Health Connect pushes are now automatically split into smaller chunks for reliable delivery ## 4.3.193 * Added Nutrition Facts Label Recognition for extracting nutritional information from label photos. For more information please visit our [documentation](https://docs.spikeapi.com). * `recognizeNutritionFactsLabel({ imageBase64, config? })` - Analyze a nutrition facts label image and extract nutritional data * Added methods for manual nutrition record management: * `createNutritionRecord({ nutritionRecord })` - Insert a new nutrition record with custom data * `replaceNutritionRecord({ nutritionRecord })` - Replace an existing nutrition record completely * Added `NutritionRecordUtils` for creating and manipulating nutrition records: * `NutritionRecordUtils.create(options)` - Create a new nutrition record with specified properties * `withIngredient(record, ingredient)` - Add an ingredient to a record * `withoutIngredient(record, ingredient)` - Remove an ingredient from a record * `withServingSize(record, servingSize, unit)` - Update serving size and unit * Added `NutritionRecordIngredientUtils` for creating and manipulating ingredients: * `NutritionRecordIngredientUtils.create(options)` - Create a new nutrition record ingredient * `withNutritionalField(ingredient, nutritionalField, value)` - Set a nutritional field value * `withoutNutritionalField(ingredient, nutritionalField)` - Remove a nutritional field * `withScaledServingSize(ingredient, newServingSize)` - Scale serving size and nutritional values proportionally * Moved metadata out of `NutritionRecord` into `NutritionRecordAnalysisResult` * `NutritionRecord` no longer contains `status` and `failureReason` fields * These fields are now part of `NutritionRecordAnalysisResult` * `NutritionRecordAnalysisResult` now has a `nutritionRecord` field containing the nutrition data * Nutrition API methods now return `NutritionRecordAnalysisResult`: * `analyzeNutrition()` - returns `NutritionRecordAnalysisResult` * `getNutritionRecords()` - returns `NutritionRecordAnalysisResult[]` * `getNutritionRecord()` - returns `NutritionRecordAnalysisResult | null` * `updateNutritionRecordServingSize()` - returns `NutritionRecordAnalysisResult` * `createNutritionRecord()` - returns `NutritionRecordAnalysisResult` * `replaceNutritionRecord()` - returns `NutritionRecordAnalysisResult` * Update Android SDK to `4.3.122` * Update iOS SDK to `4.3.141` * IMPORTANT! If after updating you get errors while building iOS app please do following: 1. `rm -rf ios/Pods/ ios/Podfile.lock` - delete old pods 2. `cd ios; pod install --repo-update` - install updated pods 3. Open project in XCode 26 and do a full clean: `Product -> Clean build folder` or `Shift + Command + K` ## 4.3.183 * Added Nutrition AI features for analyzing food images. For more information please visit our [documentation](https://docs.spikeapi.com). * `analyzeNutrition({ imageBase64, consumedAt?, config? })` - Analyze food image synchronously and wait for results * `submitNutritionForAnalysis({ imageBase64, consumedAt?, config? })` - Analyze food image asynchronously, returns record ID * `getNutritionRecords({ from, to })` - Retrieve nutrition records for a date range * `getNutritionRecord({ id })` - Get a specific nutrition record by ID * `deleteNutritionRecord({ id })` - Delete a nutrition record * `updateNutritionRecordServingSize({ id, servingSize })` - Update serving size for a nutrition record * Added new `Provider`: * luna * Added new `ProviderSource` types: * fitbitSkinTemperatureSummary * garminWellnessSkinTemperatureSummary * lunaSleep * Removed `ProviderSource` types: * fitbitUserActivitiesList * fitbitUserActivitiesDate * fitbitUserActivitiesHeartDate * fitbitUserActivitiesHeartDateInterday * withingsMeasureGetWorkouts * withingsMeasureGetActivity * withingsMeasureGetMeas * Removed `ActivityTag`: * onBicycle * Removed `Provider`: * wahoo * Update Android SDK to `4.3.112` * Update iOS SDK to `4.3.131` ## 4.3.173 * Update Android SDK to `4.3.102` * Enable Health Connect integration in the admin console right after enabling integration * Enable Samsung Health Data integration in the admin console right after enabling integration * Update iOS SDK to `4.3.121` * Enable HealthKit integration in the admin console right after requesting permissions ## 4.3.163 * Added ability to read mindfulness activities: ``` spikeConnection.getActivities( config: ActivityConfig(activityCategories: [ActivityCategory.mindfulness]) from: dateFrom, to: dateTo ) ``` * Added ability to check Android Health Connect features availability: ``` const isMindfulessAvailable = spikeConnection.isHealthConnectFeatureAvailable(HealthConnectFeature.FEATURE_MINDFULNESS_SESSION) ``` * Added new ActivityTag types: * mindfulness * Added new ActivityType types: * ecgMeasurement * triathlon * biathlon * duathlon * rollerblading * skateboarding * skating * calisthenics * weightLifting * canoeing * floorball * jiuJitsu * diving * orienteering * bootcamp * motorsports * horseriding * paragliding * multisport * bloodTest * mindfulnessSession * Added new ProviderSource types: * appleHealthkitMindfulness * healthConnectMindfulnessSession * Added a new ActivityCategory type: * mindfulness * Update Android SDK to `4.3.92` * Updated Health connect to `1.1.0-rc03` * Updated compileSdk to `36` * Updated gradle to `8.13` * Update iOS SDK to `4.3.111` ## 4.3.153 * Added documentation ## 4.3.143 * Fixed building iOS on Expo 54+ with New Architecture * Updated Expo android manifest script * Update Android SDK to `4.3.82`: * Workaround for the Android 15 bug to make background delivery more resilient * More efficient pushes to Spike API ## 4.3.133 * New `Spike.setLogCallback()` method to receive logs from the native SDK: ``` await Spike.setLogCallback((level, message) => { if (level === LogLevel.verbose) { return } console.log(`[SpikeSDK.${level.toUpperCase()}] ${message}`); }); ``` * Fixed `getBackgroundDeliveryConfig()` method to be `async getBackgroundDeliveryConfig(): Promise` * Update Android SDK to `4.3.72`: * Allow reading `SPO2` and `SWIMMING_LENGTHS` in `getActivities` * Allow reading `SPO2` and `SKIN_TEMPERATURE` in `getSleeps` * Update iOS SDK to `4.3.101`: * Improved how sleep data is read for sleep score ## 4.3.123 * Update Android SDK to `4.3.62`: * Optimized background delivery ## 4.3.113 * Added `.coros` `Provider` * Updated `ProviderSource` enum * Update Android SDK to `4.3.52`: * Added `Provider.COROS` * Updated `ProviderSource` enum * Added new Statistic types: * STRESS\_SCORE * RECOVERY\_SCORE * ACTIVITY\_SCORE * Added new Metric types for `getSleep` request: * SLEEP\_DURATION * SLEEP\_DURATION\_AWAKE * SLEEP\_DURATION\_DEEP * SLEEP\_DURATION\_LIGHT * SLEEP\_DURATION\_NAP * SLEEP\_DURATION\_REM * SLEEP\_EFFICIENCY * SLEEP\_INTERRUPTIONS * SLEEP\_LATENCY * SLEEP\_SCORE * Update iOS SDK to `4.3.91`: * Added `.coros` `Provider` * Updated `ProviderSource` enum * Improved the way data is sent to API to reduce the size and speed of calls to Spike * Added new Statistic types: * stressScore * recoveryScore * activityScore * Added new Metric types for `getSleep` request: * sleepDuration * sleepDurationAwake * sleepDurationDeep * sleepDurationLight * sleepDurationNap * sleepDurationRem * sleepEfficiency * sleepInterruptions * sleepLatency * sleepScore * Better keychain compatibility NOTE: If you use the keychain in your app with a service name equal to your bundle identifier, please check if it contains key named `spikeApiToken` and delete it. ## 4.3.103 * New metric type: * `glucose` * Update Android SDK to `4.3.42`: * New `MetricType.GLUCOSE` available in both Health Connect and Samsung Health * Update Samsung Health Data SDK to version 1.0.0 (available only on Android 29+) * Update iOS SDK to `4.3.81`: * Added `MetricType`: `.glucose` ## 4.3.93 * The old `SpikeSDK` API has been deprecated * Added `disableHealthKitIntegration` and `isHealthKitIntegrationEnabled` functions for iOS HealthKit * Update Android SDK to `4.3.32`: * Switch to protobuf java-lite 3.25.5 for better compatibility with firebase * The old `SpikeSDK` API has been deprecated * Update iOS SDK to `4.3.71`: * The old `SpikeSDK` API has been deprecated * Added `disableHealthKitIntegration` and `isHealthKitIntegrationEnabled` functions in spike connection ## 4.3.83 * New providers added: dexcom, freestyleLibre, huawei, strava * Added .distanceSwimming into the list of metrics available in activities * Updated native iOS SDK Version to `4.3.61` * Updated native Android SDK Version to `4.3.22` * Fixed prop names in SpikeRecord and Statistic data models ## 4.3.73 * Updated React native to 0.79 * Enhanced Expo support: * Added a plugin to automatically configure the Android app by updating the necessary fields in `build.gradle` files and `AndroidManifest.xml`, including handling specified permissions. * Enabled configuration of iOS HealthKit permission texts directly from the plugin settings. * Added support for enabling background delivery via plugin configuration. Config entry example for `app.json`: ``` [ "react-native-spike-sdk", { "ios": { "healthSharePermission": "$(PRODUCT_NAME) needs access to read your health data", "healthUpdatePermission": "$(PRODUCT_NAME) needs permission to save health data", "isBackgroundDeliveryEnabled": true }, "android": { "healthConnectPermissions": [ "android.permission.health.READ_BASAL_METABOLIC_RATE", "android.permission.health.READ_WEIGHT" ], "isBackgroundDeliveryEnabled": true } } ] ``` ## 4.3.63 * Added Samsung Health Integration on Android * Updated native Android SDK Version to `4.3.12`: * New statistics for Health Connect: * HEARTRATE * HEARTRATE\_MAX * HEARTRATE\_MIN * New metrics for Health Connect: * HEARTRATE * HEARTRATE\_MAX * HEARTRATE\_MIN * Improved transport protocol for even faster requests to Spike API * Added Samsung Health Integration for: * `StatisticType`s: * STEPS * DISTANCE\_TOTAL * CALORIES\_BURNED\_ACTIVE * CALORIES\_BURNED\_TOTAL * CALORIES\_BURNED\_BASAL * Added new `Provider`: * SAMSUNG\_HEALTH\_DATA * Added new `ProviderSource`: * SAMSUNG\_HEALTH\_DATA\_AGGREGATION * Renamed `MetricType`s: * SLEEP\_BREATHING\_RATE to BREATHING\_RATE * SLEEP\_BREATHING\_RATE\_MIN to BREATHING\_RATE\_MIN * SLEEP\_BREATHING\_RATE\_MAX to BREATHING\_RATE\_MAX * Updated native iOS SDK Version to `4.3.51`: * Added new `Provider`: * samsungHealthData * Added new `ProviderSource`: * samsungHealthDataAggregation * Renamed `MetricTypes`: * `sleepBreathingRate` to `breathingRate` * `sleepBreathingRateMin` to `breathingRateMin` * `sleepBreathingRateMax` to `breathingRateMax` ## 4.3.53 * Catch all exception types in the Android module ## 4.3.43 * Added missing parameter in `getHealthConnectPermissions` ## 4.3.33 * Updated native iOS SDK Version to `4.3.31` * Improve how statistics are read from HealthKit * New metric types: * bodyFat * bodyFatMax * bodyFatMin * bodyBoneMass * bodyMassIndex * bloodPressureSystolic * bloodPressureSystolicMax * bloodPressureSystolicMin * bloodPressureDiastolic * bloodPressureDiastolicMax * bloodPressureDiastolicMin * New fields in `UserProperties`: * bodyBoneMass * bodyFat * bodyMassIndex * Updated `getIntegrationInitUrl`. Now it accepts `IntegrationInitConfig` config object where you can pass: * `redirectUri`: will override the one set in admin console * `state`: when the authorization server redirects back to the client, it includes the `state` value originally sent * `providerUserIdentifier`: at the moment used (and required) only when integrating with ultrahuman * New provider: `ultrahuman` When integrating with Ultrahuman, you have to provide ultrahuman user email in `IntegrationInitConfig.providerUserIdentifier`. Example: ``` spikeConnection.getIntegrationInitUrl(provider: .ultrahuman, config: IntegrationInitConfig(providerUserIdentifier: "user@mail.com")) ``` * Improved transport protocol for background delivery * Updated native Android SDK Version to `4.2.72` * New `MetricType`s added: * BODY\_FAT * BODY\_FAT\_MAX * BODY\_FAT\_MIN * BODY\_BONE\_MASS * BODY\_MASS\_INDEX * BLOOD\_PRESSURE\_SYSTOLIC * BLOOD\_PRESSURE\_SYSTOLIC\_MIN * BLOOD\_PRESSURE\_SYSTOLIC\_MAX * BLOOD\_PRESSURE\_DIASTOLIC * BLOOD\_PRESSURE\_DIASTOLIC\_MIN * BLOOD\_PRESSURE\_DIASTOLIC\_MAX * New fields in `UserProperties`: * BODY\_FAT * BODY\_BONE\_MASS * BODY\_MASS\_INDEX * Updated `getIntegrationInitUrl`. Now it accepts `IntegrationInitConfig` config object where you can pass: * `redirectUri`: will override the one set in admin console * `state`: when the authorization server redirects back to the client, it includes the `state` value originally sent * `providerUserIdentifier`: at the moment used (and required) only when integrating with ultrahuman * New provider: `ultrahuman` When integrating with Ultrahuman, you have to provide Ultrahuman user email in `IntegrationInitConfig.providerUserIdentifier`. Example: ``` spikeConnection.getIntegrationInitUrl(provider = Provider.ULTRAHUMAN, config = IntegrationInitConfig(providerUserIdentifier = "user@mail.com")) ``` * Updated native iOS SDK Version to `4.3.41` * IntegrationInitConfig is now codable * Additional check in `getIntegrationInitUrl` for email in ultrahuman integration * Updated native Android SDK Version to `4.2.82` * Added `IntegrationInitConfigUtils` for usage in Flutter and React Native libraries * Additional check in `getIntegrationInitUrl` for email in ultrahuman integration * New provider: `ultrahuman` When integrating with Ultrahuman, you have to provide ultrahuman user email in `IntegrationInitConfig.providerUserIdentifier`. Example: ``` spikeConnection.getIntegrationInitUrl(provider: .ultrahuman, config: {providerUserIdentifier: "user@mail.com"}) ``` ## 4.3.23 * Updated native iOS SDK Version to `4.3.21` * Improve how statistics are read from HealthKit * Updated native Android SDK Version to `4.2.62` * Added consumer proguard rules ## 4.3.13 * Updated native iOS SDK Version to `4.3.11` * Improved transport protocol for even faster requests to Spike API * New statistics: heartrate, heartrateMax, heartrateMin, heartrateResting * Updated native Android SDK Version to `4.2.52` * New statistics: * HEARTRATE\_RESTING * SLEEP\_DURATION\_TOTAL * New statistics (only from non-HealthConnect providers): * HEARTRATE * HEARTRATE\_MIN * HEARTRATE\_MAX * Metric types updates * Better proguard settings for uniqueness of generated class names * Fix for statistics in different time zones ## 4.2.73 * Updated readme file ## 4.2.63 * Updated native iOS SDK Version to iOS 4.2.41 * Fix date format in JSON push ## 4.2.53 * Fixed typo in method name: `getGrantedHealthKitPermissions` in now properly called `getGrantedHealthConnectPermissions` ## 4.2.43 * Add support for React Native New Architecture (interop) ## 4.2.33 * New metrics: * spo2 * bodyTemperature * skinTemperature (available only on getSleep()) * sleepBreathingRate (available only on getSleep()) * sleepBreathingRateMin (available only on getSleep()) * sleepBreathingRateMax (available only on getSleep()) * sleepBreathingRateAvg (available only on getSleep()) * Updated native iOS SDK Version to iOS 4.2.31 * Updated native Android SDK Version to 4.2.42 * Added sleepScore property in the Record type ## 4.2.23 * New metric: `vo2Max` available in `getRecords` and `getActivities` * Changed hrv metric names: `hrvRmssd`, `hrvSdnn` * New statistic: `sleepScore` * Updated native iOS SDK Version to iOS 4.2.21 * New `getUserProperties` method for reading: weight, height, timezone, birthdate, gender * Updated native Android SDK Version to 4.2.32 * New `getUserProperties` method for reading: weight, height, timezone from Health Connect * New `getUserProperties` method for reading: birthdate, gender from other providers ## 4.2.13 * Android Spike SDK: 4.2.12 * iOS Spike SDK: 4.2.11 * 🔴 Breaking Change: Update to `Spike.createConnectionAPIv3`. The method `Spike.createConnectionAPIv3({appId:, authToken:, customerEndUserId:})` has been updated to: `Spike.createConnectionAPIv3({applicationId:, signature:, endUserId:})` * The `applicationId` parameter must now be provided as an **`number`**. * The `signature` parameter now requires an **`HMAC-SHA256` signed user ID**. * ⚠️ **Security Notice:** * **Do not store your HMAC signing key within the application itself**, as this poses a security risk. * Instead, generate and provide the signature from your backend. * **Legacy Support**: For **development purposes only**, the previous connection flow remains available under the renamed method: `Spike.createConnectionAPIv3Legacy({appId:, authToken:, customerEndUserId:})` ## 4.1.23 Fixed TypeScript definitions ## 4.1.13 * Background delivery * Android Spike SDK: 4.1.12 * Add background delivery (see documentation for more information) * Health Connect library updated to 1.1.0-alpha11 * Compile SDK and target SDK updated to 35 * Updated kotlin version to 1.9.25 * Updated other dependencies * Add log callback * Update gradle to 8.8.0 * iOS Spike SDK: 4.1.11 * Add background delivery * Add log callback * Send more sleep data ## 4.0.23 * Android Spike SDK: 4.0.22 * iOS Spike SDK: 4.0.11 ## 4.0.13 * Android Spike SDK: 4.0.12 * iOS Spike SDK: 4.0.11 * Completely new SDK! Please see our official documentation for more details and usage instructions ## 2.5.8 * iOS Spike SDK: 2.4.5 * Android Spike SDK: 3.1.6 * Android: Update steps intraday to better check for manual entries * Android: Add permissions specific to steps\_intraday ## 2.5.7 * iOS Spike SDK: 2.4.5 * Android Spike SDK: 3.1.5 * Android: check for permissions before reading additional sleep data ## 2.5.6 * iOS Spike SDK: 2.4.5 * Android Spike SDK: 3.1.4 * Android: update to the newest SDK version ## 2.5.5 * Android Spike SDK: 3.1.1 * iOS Spike SDK: 2.4.5 * iOS: update to the newest SDK version ## 2.5.4 * Android Spike SDK: 3.1.1 * iOS Spike SDK: 2.4.4 * iOS: Read more distance fields in a workout object from HealthKit ## 2.5.3 * Android Spike SDK: 3.1.1 * iOS Spike SDK: 2.4.2 * Pin iOS SDK version ## 2.5.2 * iOS Spike SDK: 2.4.2 * iOS: Add customer user id validation * Android Spike SDK: 3.1.1 * Android: Better way to authenticate with API * Android: Add saved session validation before connection is established * Android: Add customer user id validation. ## 2.5.1 * Android Spike SDK: 3.0.19 * iOS Spike SDK: 2.4.1 * iOS: Fix an issue when the timezone changes while the app is running. ## 2.5.0 * Android Spike SDK: 3.0.19 * iOS Spike SDK: 2.4.0 * iOS: Add ECG data type. ## 2.4.5 * iOS Spike SDK: 2.3.2 * Android Spike SDK: 3.0.19 * Android: Fix an issue caused by calling `SpikeSDK` methods from different threads at the same time. ## 2.4.4 * Android Spike SDK: 3.0.19 * iOS Spike SDK: 2.3.2 * iOS: Fix an issue caused by calling `SpikeSDK` methods from different threads at the same time. ## 2.4.3 * iOS Spike SDK: 2.3.2 * Android Spike SDK: 3.0.19 * Add Background deliveries support for Expo-managed projects. ## 2.4.2 * Android Spike SDK: 3.0.19 * iOS Spike SDK: 2.3.2 * iOS: Add trigger extractAndPostData request done from the background. ## 2.4.1 * Android Spike SDK: 3.0.19 * iOS Spike SDK: 2.3.1 * iOS: Send cycling parameters with Activities stream data * PROD & DEV environments are no longer supported. ## 2.3.7 * iOS Spike SDK: 2.2.3 * Android Spike SDK: 3.0.19 * Android: Add new logic for detecting changes in intraday\_steps metadata. * Android: Add safeguard for to handle multiple sources of sleep data and avoid illogical values. ## 2.3.6 * Android Spike SDK: 3.0.17 * iOS Spike SDK: 2.2.3 * Add providerTimestamp to Activities Summary data model. ## 2.3.5 * Android Spike SDK: 3.0.17 * Android: Remove the limitation that prevented values from being sent when the requested period is more than a day. * Android: Now calculating stage time by stages in sleep data. * iOS Spike SDK: 2.2.2 * iOS: Include intraday entries of a Steps intraday data type for range requests. ## 2.3.3 * Android Spike SDK: 3.0.15 * iOS Spike SDK: 2.2.1 * Add a metadata object for Spike intraday entries. ## 2.3.2 * iOS Spike SDK: 2.2.0 * Android Spike SDK: 3.0.11 * Android: add safeguards for edge cases when a function could be called without necessary data. ## 2.3.1 * iOS Spike SDK: 2.2.0 * Android Spike SDK: 3.0.11 * Android: improve Android wrapper. ## 2.3.0 * Android Spike SDK: 3.0.11 * iOS Spike SDK: 2.2.0 * iOS: SpikeData wrapper model aligned with server. ## 2.2.11 * Android Spike SDK: 3.0.10 * iOS Spike SDK: 2.1.14 * iOS: Fix date range parsing. ## 2.2.10 * iOS Spike SDK: 2.1.13 * Android Spike SDK: 3.0.10 * Android: Step intraday data improvements. ## 2.2.9 * Android Spike SDK: 3.0.8 * iOS Spike SDK: 2.1.13 * Step intraday data improvements. ## 2.2.8 * Android Spike SDK: 3.0.8 * iOS Spike SDK: 2.1.12 * iOS: Step intraday data improvements. ## 2.2.7 * iOS Spike SDK: 2.1.11 * Android Spike SDK: 3.0.8 * Allow requesting multiple permission for Android using `requestHealthPermissions` method. Provide a single Spike data type or array of Spike data types. ## 2.2.6 * Android Spike SDK: 3.0.8 * iOS Spike SDK: 2.1.11 * iOS: Add a method to check Health Store data availability (isHealthDataAvailable). ## 2.2.5 * iOS Spike SDK: 2.1.10 * Android Spike SDK: 3.0.8 * Android: Can get permission contract before SpikeConnection is created. * Android: Update package checker function to avoid unnecessary check for Android 14 and up versions. ## 2.2.4 * iOS Spike SDK: 2.1.10 * Android Spike SDK: 3.0.6 * Android: Move permission checking before data extraction to the SDK connection layer ## 2.2.3 * iOS Spike SDK: 2.1.10 * Android Spike SDK: 3.0.5 * Android: Extracting heart data fixed ## 2.2.2 * iOS Spike SDK: 2.1.10 * Android Spike SDK: 3.0.4 * Android: Added support for Android 14. * Android: Fixed some issues with data requesting for a specified date range. ## 2.2.1 * Android Spike SDK: 3.0.2 * iOS Spike SDK: 2.1.10 * iOS: Spike SDK now uses ISO8601 standardized Calendar. ## 2.2.0 * iOS Spike SDK: 2.1.9 * Android Spike SDK: 3.0.2 * Android: Activity summary and activity stream can be called without granting all their permissions. Returned data depends on which permissions were provided. * Android: Android Permission requests are divided by data types. * SpikeConnection method `checkPermissionsGranted` now requires Spike data type. * SpikeConnection method `requestHealthPermissions` now requires Spike data type. See more: [Permissions section](/sdk-docs/rn/setup#android-permissions) * Android: Works up to Android 13. # React Native SDK Logging Source: https://docs.spikeapi.com/sdk-docs/react-native/logging Getting logs for troubleshooting and debugging from Spike SDK for React Native platform. ## Code Examples To receive logs from Spike SDK for React Native, use the following code preferably before creating a connection: ```javascript theme={null} await Spike.setLogCallback((level, message) => { if (level === LogLevel.verbose) { return } console.log(`[SpikeSDK.${level.toUpperCase()}] ${message}`); }); ``` # React Native SDK Nutrition AI Source: https://docs.spikeapi.com/sdk-docs/react-native/nutrition-ai Analyze food images and retrieve nutritional information using AI-powered analysis in your React Native 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 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](#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. ```javascript theme={null} 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): ```javascript theme={null} // 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. ```javascript theme={null} 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. ```javascript theme={null} 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](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. ## Configuration Options Customize the analysis using `NutritionalAnalysisConfig`: ```javascript theme={null} 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` ```typescript theme={null} 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 ```typescript theme={null} 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](/technical-references/nutritional_fields) for all available fields. ## Managing Nutrition Records ### List Records by Date Range Retrieve all nutrition records within a specified date range: ```javascript theme={null} 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: ```javascript theme={null} 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: ```javascript theme={null} 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): ```javascript theme={null} 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: ```typescript theme={null} 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` ```typescript theme={null} enum NutritionRecordStatus { pending = "pending", processing = "processing", completed = "completed", failed = "failed", unknown = "_unknown" } ``` ### `NutritionalUnit` ```typescript theme={null} enum NutritionalUnit { g = "g", // grams mg = "mg", // milligrams mcg = "mcg", // micrograms ml = "ml", // milliliters kcal = "kcal", // kilocalories unknown = "_unknown" } ``` ### `NutritionRecordIngredient` ```typescript theme={null} 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: ```typescript theme={null} 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: ```javascript theme={null} 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: 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: ```javascript theme={null} // ❌ 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: ```javascript theme={null} 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](/nutrition-ai/async) 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: ```javascript theme={null} // 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 ```javascript theme={null} 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 ( Analyzing your meal... ); } // Render result or error... } ``` ## 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 # React Native SDK Overview Source: https://docs.spikeapi.com/sdk-docs/react-native/overview Overview of SDK-based integrations with health platforms including common principles, integration creation process, and data extraction methods for mobile health data management. # Common principles SDK-based integrations (Apple Health Kit, Android Health Connect, and Samsung Health Data) have a few common principles that separate them from all other providers. * Data is stored only on the device (phone) therefore, requires to be actively extracted * Permission control is very gradual (each metric reading requires approval) * Data becomes available instantly as it's recorded * Integration requires active management through SDKs # Creating integrations Creating these integrations does not follow the OAuth path. Meaning * there will be no redirecting to the provider authorization page, * and therefore no callback to URL after permissions are granted. * Permissions are granted locally, on the phone itself, by triggering SDK methods. * Permissions are also managed by default OS schemas, application users navigating settings menus, are granted for application package (reinstalling the app might require reauthorization), can be revoked or ignored when requested by OS based on their policies. The schema below should explain the flow and explain the sequence of events, the moment integration gets created. # Data extraction Data stored only on the user's mobile device's local hardware (encrypted at rest). To make data available over API, first you must select and call SDK functions dedicated to sleep, workouts or other metrics reading. The schema below should explain the flow and explain the sequence of events, the moment when data becomes available for reading over API and SDK. # Events sequence schema SDK integration lifecycle # React Native SDK Plugin Expo Source: https://docs.spikeapi.com/sdk-docs/react-native/plugin-expo Using Expo plugin with Spike SDK for React Native in your project. Use the [Expo config plugin](https://docs.expo.io/guides/config-plugins/) to configure the Spike SDK for React Native apps. ## Expo Installation This package cannot be used in the "Expo Go" app because [it requires custom native code](https://docs.expo.dev/workflow/customizing/). The plugin automatically configures both iOS and Android platforms for the Spike SDK. You can find more information about what is being setup in [React Native setup guide](/sdk-docs/rn/setup). First install the package with yarn, npm or [`npx expo install`](https://docs.expo.dev/more/expo-cli/#installation). ```sh theme={null} yarn install react-native-spike-sdk ``` After installing this npm package, add the [config plugin](https://docs.expo.io/guides/config-plugins/) to the [`plugins`](https://docs.expo.io/versions/latest/config/app/#plugins) array of your `app.json` or `app.config.js`: ```json theme={null} { "expo": { "plugins": [ [ "react-native-spike-sdk", { "ios": { "healthSharePermission": "Custom health share permission", "healthUpdatePermission": "Custom health update permission", "isBackgroundDeliveryEnabled": true }, "android": { "isBackgroundDeliveryEnabled": true, "healthConnectPermissions": [ "android.permission.health.READ_STEPS", ] } } ] ] } } ``` Next, rebuild your app as described in the ["Adding custom native code"](https://docs.expo.dev/workflow/customizing/) guide. ## API The plugin provides configuration options for both iOS and Android platforms. Every time you change the configuration or plugins, you'll need to rebuild (and `prebuild`) the native app. If no extra properties are added, defaults will be used. ### iOS Configuration * `healthSharePermission` (*string*): Sets the iOS `NSHealthShareUsageDescription` permission message to the `Info.plist`. Defaults to `Allow $(PRODUCT_NAME) to check health info`. * `healthUpdatePermission` (*string*): Sets the iOS `NSHealthUpdateUsageDescription` permission message to the `Info.plist`. Defaults to `Allow $(PRODUCT_NAME) to update health info`. * `isBackgroundDeliveryEnabled` (*boolean*): Adds `com.apple.developer.healthkit.background-delivery` entitlement to the iOS project. Defaults to `true`. ### Android Configuration * `isBackgroundDeliveryEnabled` (*boolean*): Whether to enable background health data deliveries. Defaults to `true`. * `healthConnectPermissions` (*string\[]*): Array of Health Connect Android permissions to add to the manifest. Defaults to `[]`. Extensive list of available permissions: ```text theme={null} android.permission.health.READ_NUTRITION android.permission.health.READ_ACTIVE_CALORIES_BURNED android.permission.health.READ_TOTAL_CALORIES_BURNED android.permission.health.READ_STEPS android.permission.health.READ_DISTANCE android.permission.health.READ_ELEVATION_GAINED android.permission.health.READ_RESTING_HEART_RATE android.permission.health.READ_HEART_RATE_VARIABILITY android.permission.health.READ_FLOORS_CLIMBED android.permission.health.READ_BASAL_METABOLIC_RATE android.permission.health.READ_SLEEP android.permission.health.READ_HEART_RATE android.permission.health.READ_EXERCISE android.permission.health.READ_SPEED android.permission.health.READ_POWER android.permission.health.READ_OXYGEN_SATURATION android.permission.health.READ_BLOOD_GLUCOSE android.permission.health.READ_RESPIRATORY_RATE android.permission.health.READ_WEIGHT android.permission.health.READ_HEIGHT android.permission.health.READ_BODY_FAT android.permission.health.READ_LEAN_BODY_MASS android.permission.health.READ_BODY_WATER_MASS android.permission.health.READ_BODY_TEMPERATURE android.permission.health.READ_BLOOD_PRESSURE android.permission.health.READ_BONE_MASS ``` ## Capabilities This plugin will enable the iOS `com.apple.developer.healthkit` entitlement, but to sync this with the bundle identifier's production capabilities you'll need to do one of two things: * Automatic: Build the app with [EAS build](https://docs.expo.io/build/introduction/) * Manual: Visit [Apple developer portal](https://developer.apple.com/account/resources/identifiers/list) and enable the HealthKit capability for your bundle identifier before building for production. This can also be done via Xcode. # React Native SDK Setup Source: https://docs.spikeapi.com/sdk-docs/react-native/setup This document provides a setup guide of the Spike SDK for React Native platform. ## Version **Current React Native SDK Version:** `4.7.13` ## Resources * React Native Package: [Available here](https://www.npmjs.com/package/react-native-spike-sdk) * API Reference for Spike SDK: [Available here](https://spike_api.gitlab.io/spike-react-native-sdk) * Example app: [Available here](https://gitlab.com/spike_api/public/spike-sdk-examples/-/tree/master/react-native-v3) ## Requirements * **iOS Version**: `13.0+`. * **Xcode 26** is required. * **Android Version**: `9.0+` (Level 28, P, Pie) ## SDK Installation Install the react-native-spike-sdk package from [npm](https://www.npmjs.com/package/react-native-spike-sdk): ```text theme={null} yarn add react-native-spike-sdk ``` ## iOS Setup Guide Use `pod install` and `pod update` commands from `ios/` folder of your app to install/update `SpikeSDK`. ### iOS Signing & Capabilities To add HealthKit support to your application's Capabilities. * Open the `iOS/` folder of your project in Xcode * Select the project name in the left sidebar * Open the Signing & Capabilities section * In the main view select '+ Capability' and double-click HealthKit More details you can find [here](https://developer.apple.com/documentation/healthkit/setting_up_healthkit). ### Info.plist Add HealthKit permissions descriptions to your `Info.plist` file: ```xml theme={null} NSHealthShareUsageDescription We will use your health information to better track workouts. NSHealthUpdateUsageDescription We will update your health information to better track workouts. ``` ## Android Setup Guide To add the SDK to your project, you have to add the following to your project's `build.gradle` file in the repositories block. ```gradle theme={null} allprojects { repositories { // Other repositories maven { url 'https://gitlab.com/api/v4/projects/43396247/packages/maven' } } } ``` ### Android Permissions Include the necessary health permissions in your AndroidManifest.xml to fully leverage the Spike SDK and access data from apps integrated with Health Connect. Please refer to [this guide](https://developer.android.com/health-and-fitness/guides/health-connect/get-started#declare-permissions) for details on the required permissions. **Note**: Only request permissions essential to your app’s functionality. Requesting unused permissions may lead to app store rejections. ```xml theme={null} ``` Add an intent filter to your activity definition so that you can request the permissions at runtime. ```xml theme={null} ``` To handle Android 14, you also need to add activity-alias to your AndroidManifest.xml It is just a wrapper for the activity that requests permissions, so no real activity is necessary. ```xml theme={null} ``` # React Native SDK Usage Guide Source: https://docs.spikeapi.com/sdk-docs/react-native/usage-guide Start getting Spike data in 3 steps using Spike SDK for React Native platform. ## Step 1: Create a Spike Connection To set up the Spike SDK create `SpikeConnectionV3` instance with your Spike application id, application user id and signature unique to each of your application users (more on generating signatures [here](/api-docs/authentication)): ```javascript theme={null} import Spike from 'react-native-spike-sdk'; const connection = await Spike.createConnectionAPIv3({ applicationId: 1000, signature: "signature", endUserId: "user-id" }); ``` ## Step 2: Ask User for Permissions If you plan to use Android Health Connect and/or Apple HealthKit data providers you should first get users permission to use their data: ```javascript theme={null} // This call may present user with OS native modal asking for read // permissions needed to get steps and distance data. If you want to // present it only once, you should list all the statistic types you // plan to use. Otherwise, you can call this only before you actually // want to use the data. await spikeConnection.requestHealthPermissions({ statisticTypes: [StatisticsType.steps, StatisticsType.distanceTotal] }); ``` For Android there are more methods that let you get permissions granted by user, check if Health Connect is installed or should be updated, etc. If you are OK with Spike SDK handling these, you can use `requestHealthPermissions` only. On iOS `requestHealthPermissions` is the only method that has to be called. Keep in mind that you should do it before trying to read data from Android Health Connect or Apple HealthKit. ## Step 3: Get Data The maximum permitted query date range on Android Health Connect is 90 days There are 4 types of data you can retrieve from Spike: * **Statistics** are calculated values derived from records. * **Activities** are data about user's activities or workouts. * **Sleep** is data about user's sleep. * **Records** consist of the raw data points collected from user devices or applications. ### Statistics Now you can read hourly statistics data of steps and distance for today: ```javascript theme={null} const now = new Date(); const start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1); const end = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1); const statistics = await spikeConnection.getStatistics( [StatisticsType.steps, StatisticsType.distanceTotal], start, end, StatisticsInterval.hour, new StatisticsFilter(false, [Provider.apple, Provider.healthConnect]) ) ``` where: ```javascript theme={null} export enum StatisticsType { steps = "steps", distanceTotal = "distance_total", distanceWalking = "distance_walking", distanceCycling = "distance_cycling", distanceRunning = "distance_running", caloriesBurnedTotal = "calories_burned_total", caloriesBurnedBasal = "calories_burned_basal", caloriesBurnedActive = "calories_burned_active", sleepScore = "sleep_score", sleepDurationTotal = "sleep_duration_total", heartrate = "heartrate", heartrateMax = "heartrate_max", heartrateMin = "heartrate_min", heartrateResting = "heartrate_resting", unknown = "_unknown" } enum StatisticsInterval { day, hour } // Filter class StatisticsFilter { excludeManual: boolean = false providers: Provider[] | undefined activityTags: ActivityTag[] | undefined constructor({ excludeManual = false, providers = undefined, activityTags = undefined }: StatisticsFilterConstructorParameters) { this.excludeManual = excludeManual; this.providers = providers; this.activityTags = activityTags; } } // Result: interface Statistic { intervalStart: string; intervalEnd: string; intervalDuration: number; statisticType: StatisticsType; statisticValue: number; statisticUnit: Unit; rowCount: number | null; recordIds: UUID[] | null; } ``` ### Records ```javascript theme={null} const now = new Date(); const start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1); const end = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1); const records = await spikeConnection.getRecords({ ofTypes: metricTypes, from: start, to: end, filter: new StatisticsFilter({ excludeManual: false, providers: [Provider.apple, Provider.healthConnect] }) }) ``` ```javascript theme={null} enum MetricType { heartrateMax = "heartrate_max", heartrateMin = "heartrate_min", heartrate = "heartrate", heartrateResting = "heartrate_resting", hrvRmssd = "hrv_rmssd", hrvSdnn = "hrv_sdnn", elevationMax = "elevation_max", elevationMin = "elevation_min", elevationGain = "elevation_gain", elevationLoss = "elevation_loss", ascent = "ascent", descent = "descent", caloriesBurnedActive = "calories_burned_active", caloriesBurnedBasal = "calories_burned_basal", caloriesBurned = "calories_burned", caloriesIntake = "calories_intake", stepsTotal = "steps", floorsClimbed = "floors_climbed", distanceTotal = "distance", distanceWalking = "distance_walking", distanceCycling = "distance_cycling", distanceRunning = "distance_running", distanceWheelchair = "distance_wheelchair", distanceSwimming = "distance_swimming", speed = "speed", speedMax = "speed_max", speedMin = "speed_min", airTemperatureMax = "air_temperature_max", airTemperatureMin = "air_temperature_min", spo2 = "spo2", spo2Max = "spo2_max", spo2Min = "spo2_min", longitude = "longitude", latitude = "latitude", elevation = "elevation", durationActive = "duration_active", swimmingLengths = "swimming_lenghts", swimmingDistancePerStroke = "swimming_distance_per_stroke", height = "height", weight = "weight", birthYear = "birth_year", birthDate = "birth_date", timezone = "timezone", gender = "gender", vo2Max = "vo2max", bodyTemperature = "body_temperature", skinTemperature = "skin_temperature", breathingRate = "breathing_rate", breathingRateMin = "breathing_rate_min", breathingRateMax = "breathing_rate_max", bodyFat = "body_fat", bodyFatMax = "body_fat_max", bodyFatMin = "body_fat_min", bodyBoneMass = "body_bone_mass", bodyMassIndex = "body_mass_index", bloodPressureSystolic = "blood_pressure_systolic", bloodPressureSystolicMin = "blood_pressure_systolic_min", bloodPressureSystolicMax = "blood_pressure_systolic_max", bloodPressureDiastolic = "blood_pressure_diastolic", bloodPressureDiastolicMin = "blood_pressure_diastolic_min", bloodPressureDiastolicMax = "blood_pressure_diastolic_max", unknown = "_unknown" } // Result: interface SpikeRecord { recordId: UUID; inputMethod: InputMethod | null; startAt: string; endAt: string | null; modifiedAt: string; duration: number | null; providerSlug: Provider | null; providerSource: ProviderSource | null; isSourceAggregated: boolean | null; source: RecordSource | null; metrics: { [key: string]: number } | null; sleepScore: number | null; activityTags: ActivityTag[] | null; activityType: ActivityType | null; sessions: ActivityEntry[] | null; laps: ActivityEntry[] | null; segments: ActivityEntry[] | null; splits: ActivityEntry[] | null; samples: ActivityEntry[] | null; routePoints: ActivityEntry[] | null; sleep: ActivityEntry[] | null; } ``` # React Native SDK for Samsung Health Source: https://docs.spikeapi.com/sdk-docs/react-native/usage-guide-shd Start getting Spike data in 3 steps using Spike SDK for Samsung Health Data on React Native platform. ## Requirements and Limitations Samsung Health Data is available on Android devices only! * Samsung Health Data SDK runs on devices with Android 10 (API level 29) or above. It is available on all Samsung smartphones and non-Samsung Android smartphones. * Samsung Health Data SDK works with Samsung Health. Samsung Health version 6.30.2 or higher is required. * Samsung Health Data SDK supports Java 17 or higher version. * The emulator is not supported. * Data obtained using Samsung Health Data SDK is for fitness and wellness information only. It is not for the diagnosis or treatment of any medical condition. ## Step 1: Create a Spike Connection If you already set up [Android Health Connect integration](/sdk-docs/rn/usage-guide) in your app, you should skip to [this step](#step-2%3A-ask-user-for-permissions) and use the same Spike SDK connection object. To set up the Spike SDK create `SpikeConnectionV3` instance with your Spike application id, application user id and signature unique to each of your application users (more on generating signatures [here](/api-docs/authentication)): ```javascript theme={null} import Spike from 'react-native-spike-sdk'; const connection = await Spike.createConnectionAPIv3({ applicationId: 1000, signature: "signature", endUserId: "user-id" }); ``` ## Step 2: Ask User for Permissions If you want to read data from Samsung Health, you have to ensure the user gives your app permissions. First, you have to check if Samsung Health is available on users' phone using `checkSamsungHealthDataAvailability` method: ```typescript theme={null} val availability = spikeConnection.checkSamsungHealthDataAvailability() ``` where ```typescript theme={null} interface SamsungHealthDataAvailability { status: SamsungHealthDataAvailabilityStatus; errorCode: number; message: string; } enum SamsungHealthDataAvailabilityStatus { /** * Samsung Health is not installed. Ask the user to install it. */ notInstalled = "NOT_INSTALLED", /** * The version of Samsung Health is too old. Ask users to update it. */ updateRequired = "UPDATE_REQUIRED", /** * The Samsung Health Data is installed but is disabled. */ disabled = "DISABLED", /** * Samsung Health has been installed, but the user didn't perform an initial process, such as * agreeing to the Terms and Conditions. */ notInitialized = "NOT_INITIALIZED", /** * Samsung Health returned the other error. */ errorOther = "ERROR_OTHER", /** * Samsung Health Data is available. */ installed = "INSTALLED", } ``` If Samsung Health is installed, you can ask user for permissions using `requestPermissions` method: ```typescript theme={null} // Samsung Health integration has to be enabled in Spike SDK connection before // using further methods for reading data or managing permissions: spikeConnection.enableSamsungHealthDataIntegration() spikeConnection.requestPermissionsFromSamsungHealthData( statisticsTypes = setOf(StatisticsType.STEPS, StatisticsType.DISTANCE_TOTAL) ) ``` Please note that users might only grant partial permissions. In such cases, it’s up to you to decide whether your app can function effectively with limited access. The Spike SDK itself will still operate even without full permissions; however, it may result in no data being returned in certain scenarios. You can now use `StatisticsFilter(providers = listOf(Provider.SAMSUNG_HEALTH_DATA))` to specifically retrieve data from Samsung Health. Alternatively, you can omit the providers parameter entirely and allow Spike to choose the most suitable data source based on your request. ## Step 3: Get Data Reading data is the same as in Android from [Google Health Connect](/sdk-docs/android/usage-guide#step-3%3A-get-data). The only difference is that you may want to filter results by `Provider.SAMSUNG_HEALTH_DATA` to get only Samsung Health data: ```typescript theme={null} StatisticsFilter(providers = [Provider.SAMSUNG_HEALTH_DATA]) ``` ## Background Delivery Samsung Health data can be delivered in the background the same way as Apple HealthKit or Android Health Connect. If you want to use background delivery to get Samsung Health data, you have to enable Samsung Health Data integration first: ```typescript theme={null} spikeConnection.enableSamsungHealthDataIntegration() ``` After enabling Samsung Health Data integration, you can use background delivery normally. See the [React Native SDK Background Delivery section](/sdk-docs/rn/background-delivery) for more details. ## Developer Mode for Testing To test Samsung Health Data integration on your phone, you have to enable developer mode in the Samsung Health app: 1. Tap the ‘⋮’ button of Samsung Health in the top-right. 2. Select Settings > About Samsung Health. 3. Tap the version line region quickly 10 times or more. If you are successful, the Developer mode (Samsung Health Data SDK) button is displayed. 4. Select Developer mode (Samsung Health Data SDK) . 5. Agree with the Notice about usage of the Developer mode. 6. To read data from Samsung Health with Samsung Health Data SDK, turn Developer Mode for Data Read on. After your app is ready for release, you should apply for a Samsung Partnership Agreement. For more information, please contact Spike support. The Samsung Health developer mode is ONLY intended for testing or debugging your app. It is NOT for app users. Do not provide a developer mode guide to app users. ## See Also * [Samsung error codes](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3.samsung/-samsung-health-data-availability/error-code.html) * [SamsungHealthDataPermissionManager](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3.samsung/-samsung-health-data-permission-manager/index.html) * [SamsungHealthDataAvailabilityStatus](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3.samsung/-samsung-health-data-availability-status/index.html) * [SpikeConnectionV3.enableSamsungHealthDataIntegration](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3/-spike-connection-a-p-iv3/enable-samsung-health-data-integration.html) * [SpikeConnectionV3.disableSamsungHealthDataIntegration](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3/-spike-connection-a-p-iv3/disable-samsung-health-data-integration.html) * [SpikeConnectionV3.isSamsungHealthDataIntegrationEnabled](https://spike_api.gitlab.io/spike-android-sdk/sdk/com.spikeapi.apiv3/-spike-connection-a-p-iv3/is-samsung-health-data-integration-enabled.html) # Activity Types and Tags Source: https://docs.spikeapi.com/technical-references/activity_matrix This document provides a comprehensive overview of all activities, detailing their tags and descriptions. The table below summarizes the key features supported by each activity. ## Tags Tags are utilized in queries to efficiently retrieve a set of activities that share common characteristics. The table below provides a list of all available tags, along with their descriptions and usage examples. | **Tag** | **Description** | | :------------------ | :---------------------------- | | climbing | Climbing | | cycling | Cycling | | dancing | Dancing | | ecg | ECG measurement | | gym | Indoor or outdoor gym workout | | individual\_sports | Individual sports | | lab\_report | Lab report | | martial\_arts | Martial arts | | mindfulness | Mindfulness | | on\_foot | Walking | | outdoor\_recreation | Outdoor recreation | | racket\_sports | Racket sports | | running | Running | | sleep | Sleep | | swimming | Swimming | | team\_sports | Team sports | | walking | Walking | | water\_sports | Water sports | | wheelchair | Wheelchair | | winter\_sports | Winter sports | | workout | General workout | ## Activities The table below provides details on the various activities, including a brief description of each and the associated tags. | **Activity Type** | **Description** | **Tags** | | :------------------------------ | :------------------------------- | :-------------------------------------------- | | aerobics | | `workout` | | aikido | Aikido | `martial_arts` `workout` | | american\_football | American football | `team_sports` `workout` | | archery | Archery | `individual_sports` `workout` | | australian\_football | Australian football | `team_sports` `workout` | | backcountry\_skiing | Backcountry skiing | `winter_sports` `workout` | | backcountry\_snowboarding | Backcountry snowboarding | `winter_sports` `workout` | | badminton | Badminton | `racket_sports` `workout` | | barre | Barre | `dancing` `workout` | | baseball | Baseball | `team_sports` `workout` | | basketball | Basketball | `team_sports` `workout` | | biathlon | Biathlon activity | `winter_sports` `workout` | | blood\_test | Blood Test | `lab_report` | | bmx\_cycling | BMX cycling activity | `cycling` `workout` | | boating | Boating | `water_sports` `workout` | | bootcamp | Bootcamp | `outdoor_recreation` `workout` | | bouldering | Bouldering activity | `gym` `climbing` `workout` | | bowling | Bowling | `individual_sports` `workout` | | boxing | Boxing | `martial_arts` `workout` | | calisthenics | Calisthenics | `gym` `workout` | | canoeing | Canoeing | `water_sports` `workout` | | capoeira | Capoeira | `martial_arts` `workout` | | casual\_walking | Casual walking | `on_foot` `walking` `workout` | | climbing | Climbing activity | `climbing` `workout` | | cool\_down | Cool down | `workout` | | core\_training | Core training | `gym` `workout` | | cricket | Cricket | `team_sports` `workout` | | cross\_country\_classic\_skiing | Cross-country classic skiing | `winter_sports` `workout` | | cross\_country\_skate\_skiing | Cross-country skate skiing | `winter_sports` `workout` | | crossfit | CrossFit | `gym` `workout` | | curling | Curling | `winter_sports` `team_sports` `workout` | | cycling | Cycling activity | `cycling` `workout` | | cyclocross\_cycling | Cyclocross cycling activity | `cycling` `workout` | | dancing | Dancing activity | `dancing` `workout` | | disc | Disc sports | `team_sports` `workout` | | diving | | `water_sports` `outdoor_recreation` `workout` | | downhill\_biking | Downhill biking activity | `cycling` `workout` | | duathlon | Duathlon activity | `running` `cycling` `workout` | | e\_bike\_fitness | E-bike fitness activity | `cycling` `workout` | | e\_bike\_mountain | E-bike mountain activity | `cycling` `workout` | | ecg\_measurement | | `ecg` | | elliptical | Elliptical training | `gym` `workout` | | equestrian | Equestrian | `outdoor_recreation` `workout` | | fencing | | `individual_sports` `martial_arts` `workout` | | field\_hockey | Field hockey | `team_sports` `workout` | | fishing | | `water_sports` `outdoor_recreation` `workout` | | flexibility\_training | Flexibility training | `gym` `workout` | | floorball | Floorball | `team_sports` `workout` | | functional\_fitness | Functional fitness | `gym` `workout` | | gardening | Gardening activity | `workout` | | geocaching | Geocaching | `outdoor_recreation` `workout` | | golf | Golf | `individual_sports` `workout` | | gravel\_cycling | Gravel cycling activity | `cycling` `workout` | | gym | Gym activity | `gym` `workout` | | gymnastics | Gymnastics | `gym` `workout` | | hand\_cycling | Hand cycling activity | `cycling` `workout` | | handball | Handball | `team_sports` `workout` | | hiit | High-intensity interval training | `gym` `workout` | | hiking | Hiking | `on_foot` `walking` `workout` | | horseriding | Horse riding | `outdoor_recreation` `workout` | | house\_work | House work activity | `workout` | | hunting | Hunting | `outdoor_recreation` `workout` | | ice\_hockey | Ice hockey | `team_sports` `workout` | | indoor\_cardio | Indoor cardio | `gym` `workout` | | indoor\_climbing | Indoor climbing | `gym` `workout` | | indoor\_cycling | Indoor cycling activity | `cycling` `gym` `workout` | | indoor\_hand\_cycling | Indoor hand cycling activity | `cycling` `gym` `workout` | | indoor\_rowing | Indoor rowing | `gym` `workout` | | indoor\_running | Indoor running activity | `on_foot` `running` `gym` `workout` | | jiu\_jitsu | Jiu Jitsu | `martial_arts` `workout` | | judo | Judo | `martial_arts` `workout` | | jumping | Jumping Rope | `individual_sports` `workout` | | karate | Karate | `martial_arts` `workout` | | kayaking | Kayaking | `water_sports` `workout` | | kickboxing | Kickboxing | `martial_arts` `workout` | | kiteboarding | Kiteboarding | `water_sports` `workout` | | kung\_fu | Kung Fu | `martial_arts` `workout` | | lacrosse | Lacrosse | `team_sports` `workout` | | martial\_arts | Martial arts activity | `martial_arts` `workout` | | meditation | Meditation | `gym` `workout` | | mindfulness\_session | Mindfulness Session | `mindfulness` | | mixed\_martial\_arts | Mixed martial arts | `martial_arts` `workout` | | mixed\_training | Mixed training | `gym` `workout` | | motorsports | Motorsports | `workout` | | mountain\_biking | Mountain biking activity | `cycling` `workout` | | muay\_thai | Muay Thai | `martial_arts` `workout` | | multisport | Multisport | `workout` | | netball | Netball | `team_sports` `workout` | | obstacle\_run | Obstacle running activity | `on_foot` `running` `workout` | | offshore\_grinding | Offshore grinding | `water_sports` `workout` | | onshore\_grinding | Onshore grinding | `water_sports` `workout` | | open\_water\_swimming | Open water swimming | `swimming` `workout` | | orienteering | Orienteering | `outdoor_recreation` `workout` | | other | Other activity | | | outdoor\_recreation | Outdoor recreation activity | `outdoor_recreation` `workout` | | paddle\_ball | Paddle ball | `racket_sports` `workout` | | paddling | Paddling | `water_sports` `workout` | | padel | Padel | `racket_sports` `workout` | | paintball | Paintball | `team_sports` `workout` | | paragliding | Paragliding | `outdoor_recreation` `workout` | | parkour | Parkour | `individual_sports` `workout` | | pickleball | Pickleball | `racket_sports` `workout` | | pilates | Pilates | `gym` `workout` | | platform\_tennis | Platform tennis | `racket_sports` `workout` | | play | Play activity | `workout` | | polo | Polo | `team_sports` `workout` | | racket\_sports | Racket sports activity | `racket_sports` `workout` | | racquetball | Racquetball | `racket_sports` `workout` | | recumbent\_cycling | Recumbent cycling activity | `cycling` `workout` | | road\_biking | Road biking activity | `cycling` `workout` | | rollerblading | Rollerblading activity | `workout` | | rope\_skipping | Rope skipping | `gym` `workout` | | rowing | Rowing | `water_sports` `workout` | | rugby | Rugby | `team_sports` `workout` | | running | Running activity | `on_foot` `running` `workout` | | sailing | Sailing | `water_sports` `workout` | | sedentary | Sedentary activity | | | skateboarding | Skateboarding | `workout` | | skating | Skating activity | `workout` | | skating\_skiing | Skating skiing | `winter_sports` `workout` | | skiing | Skiing | `winter_sports` `workout` | | skiing\_snowboarding | Skiing and snowboarding | `winter_sports` `workout` | | sleep | | `sleep` | | snorkeling | Snorkeling | `water_sports` `workout` | | snow\_shoeing | Snow shoeing | `winter_sports` `workout` | | snowboarding | Snowboarding | `winter_sports` `workout` | | snowmobiling | Snowmobiling | `winter_sports` `workout` | | soccer | Soccer | `team_sports` `workout` | | softball | Softball | `team_sports` `workout` | | speed\_walking | Speed walking | `on_foot` `walking` `workout` | | squash | Squash | `racket_sports` `workout` | | stair\_climbing | Stair climbing | `gym` `workout` | | stand\_up\_paddleboarding | Stand-up paddleboarding | `water_sports` `workout` | | street\_running | Street running activity | `on_foot` `running` `workout` | | strength\_training | Strength training | `gym` `workout` | | stretching | Stretching | `workout` | | stroller | Stroller | `on_foot` `walking` `workout` | | surfing | Surfing | `water_sports` `workout` | | swimming | | `swimming` `workout` | | table\_tennis | Table tennis | `racket_sports` `workout` | | taekwondo | Taekwondo | `martial_arts` `workout` | | tai\_chi | Tai Chi | `martial_arts` `workout` | | team\_sports | Team sports activity | `team_sports` `workout` | | tennis | Tennis | `racket_sports` `workout` | | track\_cycling | Track cycling activity | `cycling` `workout` | | track\_running | Track running activity | `on_foot` `running` `workout` | | trail\_running | Trail running activity | `on_foot` `running` `workout` | | treadmill\_running | Treadmill running activity | `on_foot` `running` `gym` `workout` | | triathlon | Triathlon activity | `running` `cycling` `swimming` `workout` | | ultra\_running | Ultra running activity | `on_foot` `running` `workout` | | virtual\_ride | Virtual ride activity | `cycling` `workout` | | virtual\_running | Virtual running activity | `on_foot` `running` `workout` | | volleyball | Volleyball | `team_sports` `workout` | | wakeboarding | Wakeboarding | `water_sports` `workout` | | walking | Walking activity | `on_foot` `walking` `workout` | | warm\_up | Warm-up | `gym` `workout` | | water\_polo | Water polo | `water_sports` `team_sports` `workout` | | water\_sports | Water sports activity | `water_sports` `workout` | | waterskiing | Waterskiing | `water_sports` `workout` | | weight\_lifting | Weight lifting | `gym` `workout` | | wheelchair\_push\_run | Wheelchair push run | `wheelchair` `workout` | | wheelchair\_push\_walk | Wheelchair push walk | `wheelchair` `workout` | | whitewater\_rafting | Whitewater rafting | `water_sports` `workout` | | windsurfing | Windsurfing | `water_sports` `workout` | | winter\_sports | Winter sports activity | `winter_sports` `workout` | | work | Working activity | `workout` | | workout | General workout activity | `workout` | | wrestling | Wrestling | `martial_arts` `workout` | | yoga | Yoga | `gym` `workout` | # Application Configuration Source: https://docs.spikeapi.com/technical-references/application_configuration Configure your Spike API application settings in the admin console Configure your Spike API application through the [admin console](https://admin.spikeapi.com/) to control authentication, webhooks, data retention, and integration behavior. Access the Spike API admin console to configure your application settings, manage integrations, and monitor your API usage. ## Application Identifiers ### Application ID Your unique application identifier assigned when the application is created. Use this ID for API authentication and integration management. ## Authentication Keys Access your application credentials from the admin console to authenticate and secure your API integration. Application Credentials ### HMAC Key Secret key for HMAC-based authentication. Required for `hmac` auth flow applications. Must be at least 16 characters long. For detailed implementation examples, see the [Authentication documentation](/api-docs/authentication). ### Webhook Signature Key Secret key used to sign webhook payloads for security verification. Use this to validate that webhooks are genuinely from Spike API. For implementation examples and signature verification code, see the [Webhooks documentation](/api-docs/webhooks). ## Integration Options Configure your application settings through the admin console to customize integration behavior and data handling. Application Configuration ### Default Redirect URL The URL users are redirected to after authenticating with a provider. Used when no specific redirect URL is provided in the OAuth2 flow. **Placeholders available:** * `{application_user_id}` - Your application's user identifier * `{provider_slug}` - The connected provider (e.g., "garmin", "fitbit") * `{provider_user_id}` - The user's ID from the provider **Example:** ``` https://example.com/callback?user={application_user_id}&provider={provider_slug} ``` **Predefined parameters:** The system automatically appends these query parameters: * `user_id={application_user_id}` * `provider_slug={provider_slug}` * `error={error_text}` (only when integration fails) ### Allowed Redirect Domains List of domains permitted for redirect URLs to prevent redirect attacks. You can use `redirect_uri` as a placeholder for dynamic redirects. **Example:** ``` https://example.com/callback?redirect_uri=http%3A%2F%2Fallowed-domain.com%2Fpath ``` ### Webhook URLs #### Main Webhook URL Primary endpoint for receiving real-time data updates. Called whenever user health data changes. **When triggered:** * New health data from connected providers * Data updates or corrections * Provider integration status changes #### Lab Reports Webhook URL Dedicated endpoint for lab report processing completion notifications. **When triggered:** * Lab report analysis is completed * Lab report processing fails * New lab report data becomes available #### Nutrition Records Webhook URL Dedicated endpoint for nutrition data processing notifications. **When triggered:** * Nutrition record analysis is completed * Nutrition data processing fails * New nutrition data becomes available ### Data Backfill Configuration #### Max Backfill (days) Controls how far back the system attempts to fetch historical data when a user connects a new provider. **Limitations:** * Maximum 90 days * Cannot exceed your Dataset Retention Policy setting * Provider-specific limitations may apply (e.g., some providers only provide 30 days) **Provider examples:** * Fitbit: Up to 90 days * Garmin: Up to 90 days * Oura: Up to 180 days (recommended max 30 days) * Apple HealthKit: Limited by user's device data * Android Health Connect: 30 days + optional PERMISSION\_READ\_HEALTH\_DATA\_HISTORY For complete provider-specific limitations and capabilities, see the [Provider Matrix](/technical-references/provider_matrix). For SDK-based providers (iOS HealthKit, Android Health Connect), manual data extraction is required. See the [SDK backfill documentation](/sdk-docs/ios/backfill) for implementation details. ## Best Practices ### Security * Keep authentication keys secure and rotate them regularly * Use HTTPS for all webhook and redirect URLs * Validate webhook signatures using your Webhook Signature Key * Restrict allowed redirect domains to prevent attacks ### Performance * Use dedicated webhook URLs for different data types when you need specialized handling * Set appropriate backfill limits based on your processing capacity * Consider rate limiting on your webhook endpoints ### Monitoring * Monitor webhook delivery success rates * Set up alerts for authentication failures * Track backfill completion status for new integrations ### Development vs Production * Use different applications for development and production environments * Test webhook endpoints thoroughly before going live * Start with shorter backfill periods during development # Backfill of Historical Data Source: https://docs.spikeapi.com/technical-references/backfill Comprehensive guide to backfill mechanisms for retrieving historical health data across different providers and platforms. ## About After successfully creating a Spike API integration, a data stream of wearables recorded data becomes available for the application user when making API calls and also through webhooks. Usually only data recorded from the moment of authorization is shared, but Spike has developed mechanisms to fetch data recorded retrospectively as well. This document describes the feature called **Backfill** and principles of how to use it, as well as the limitations. ## Configuration In the admin console, under Application configuration you can find the setting called **"Max Backfill (days)"**. By default it's disabled (set to nothing or 0). To enable the backfill mechanism you need to set the value to the required number of days and save it. ## Core Principles Once backfill is enabled and new integration gets created, here's what you should expect: * **Timing**: Backfill is triggered post new integration creation. Settings would have no effect on integrations that are already created and have the same data accumulated by natural lifecycle * **Data Delivery**: Data delivery (API and webhooks) is unified, but backfilling process is unique to each provider * **Asynchronous Processing**: Backfilling mostly is an asynchronous process. Seeking the best user experience for application users, they will see integration being successfully created (redirected back to configured postback URL) and the process might continue in the background ## Implementation Categories ### Manual Implementation Required Some providers require manual data extraction through SDK calls: #### Apple HealthKit Data is stored exclusively on the device (iPhone). Manual implementation is required because Apple doesn't offer any other communication except the HealthKit framework. **Limitations:** * What data the user has on their device * What permissions your app has been granted (read access for specific data types) * User's health app settings or deletions * Your retention policy settings **Implementation:** See [iOS SDK Backfill Guide](/sdk-docs/ios/backfill) for detailed implementation instructions. #### Google Health Connect Data is stored exclusively on the device (running Android). Manual implementation is required because Google doesn't offer any other communication except the Health Connect client. **Limitations:** * What data the user has on their device * What permissions your app has been granted (read access for specific data types) * User's health app settings or deletions * Your retention policy settings * 30 days period predating permission grant **Special Considerations:** * Permission request timing is not relevant - if an application user decides to approve permission a week later by going directly into Health Connect settings menu, you will be able to access data 23 days prior to installing the app * For apps targeting Android API level 34 (Android 14) and higher, Google has introduced `PERMISSION_READ_HEALTH_DATA_HISTORY` which allows access to health data recorded before the app was installed **Implementation:** See [Android SDK Backfill Guide](/sdk-docs/android/backfill) for detailed implementation instructions. ### Automatic Backfill Most third-party providers handle backfill automatically with no additional effort required from your side. Each provider has different availability windows and processing times. For complete details on each provider's backfill capabilities, availability windows, and specific limitations, refer to the [Supported Providers](/technical-references/provider_matrix) documentation. ## Best Practices ### For Manual Implementation (iOS/Android) * Keep the backfill process asynchronous for optimal user experience * Segment requests into smaller date ranges to ensure optimal performance * Implement rate limiting to avoid overwhelming the health data store * Handle permissions gracefully - users may grant partial access * Provide meaningful feedback about data access issues ### For Automatic Backfill * Listen for incoming webhooks to know when data becomes available * Be patient with the asynchronous process - some providers may take hours * Understand that data may not arrive in chronological order * Consider the varying limitations across providers when setting expectations ### General Recommendations * Set realistic backfill periods considering provider limitations * Implement robust error handling for various scenarios * Respect user privacy and data access permissions * Design your application to handle partial or delayed data availability ## Provider-Specific Details For complete details on each provider's capabilities, limitations, and backfill availability, refer to the [Supported Providers](/technical-references/provider_matrix) documentation. For platform-specific implementation guides: * [iOS SDK Backfill Guide](/sdk-docs/ios/backfill) * [Android SDK Backfill Guide](/sdk-docs/android/backfill) ## Monitoring and Troubleshooting To effectively monitor backfill operations: 1. **Webhook Monitoring**: Set up webhook listeners to track when historical data becomes available 2. **Error Handling**: Implement comprehensive error handling for permission issues, data unavailability, and provider limitations 3. **User Communication**: Provide clear feedback to users about the backfill process and expected timelines 4. **Logging**: Maintain detailed logs of backfill requests and outcomes for debugging purposes # Metric Types and Units Source: https://docs.spikeapi.com/technical-references/metrics_matrix This document provides a comprehensive overview of all metrics, detailing their providers, descriptions, units, and number of decimals for output precision. The table below summarizes the key features supported by each metric. | **Metric** | **Providers** | **Description** | **Unit** | **Precision** | | :---------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------- | :---------- | :------------ | | ascent | `garmin` `polar` `suunto` `huawei` `coros` | Total ascent | meters | 2 | | bedtime\_duration | `fitbit` `oura` `withings` `huawei` `ultrahuman` `samsung_health_data` `luna` `google_health` | Bedtime duration | ms | 0 | | blood\_pressure\_diastolic | `apple` `health_connect` `garmin` `withings` `huawei` `samsung_health_data` `omron_us` `omron_eu` | Diastolic blood pressure | mmHg | 2 | | blood\_pressure\_diastolic\_max | `apple` `health_connect` `garmin` `withings` `huawei` `samsung_health_data` `omron_us` `omron_eu` | Maximum diastolic blood pressure | mmHg | 2 | | blood\_pressure\_diastolic\_min | `apple` `health_connect` `garmin` `withings` `huawei` `samsung_health_data` `omron_us` `omron_eu` | Minimum diastolic blood pressure | mmHg | 2 | | blood\_pressure\_systolic | `apple` `health_connect` `garmin` `withings` `huawei` `samsung_health_data` `omron_us` `omron_eu` | Systolic blood pressure | mmHg | 2 | | blood\_pressure\_systolic\_max | `apple` `health_connect` `garmin` `withings` `huawei` `samsung_health_data` `omron_us` `omron_eu` | Maximum systolic blood pressure | mmHg | 2 | | blood\_pressure\_systolic\_min | `apple` `health_connect` `garmin` `withings` `huawei` `samsung_health_data` `omron_us` `omron_eu` | Minimum systolic blood pressure | mmHg | 2 | | body\_bone\_mass | `garmin` `withings` | Bone mass | g | 0 | | body\_fat | `apple` `health_connect` `fitbit` `garmin` `withings` `huawei` `samsung_health_data` `omron_us` `omron_eu` `google_health` | Body fat percentage | percentage | 2 | | body\_fat\_max | `apple` `health_connect` `fitbit` `garmin` `withings` `huawei` `samsung_health_data` `omron_us` `omron_eu` `google_health` | Maximum body fat percentage | percentage | 2 | | body\_fat\_min | `apple` `health_connect` `fitbit` `garmin` `withings` `huawei` `samsung_health_data` `omron_us` `omron_eu` `google_health` | Minimum body fat percentage | percentage | 2 | | body\_mass\_index | `apple` `health_connect` `fitbit` `garmin` `samsung_health_data` `omron_us` `omron_eu` | Body mass index | count | 4 | | body\_temperature | `apple` `health_connect` `fitbit` `oura` `polar` `withings` `samsung_health_data` `omron_us` `omron_eu` | Body temperature | celsius | 2 | | body\_temperature\_max | `apple` `health_connect` `fitbit` `oura` `polar` `withings` `samsung_health_data` `omron_us` `omron_eu` | Maximum body temperature | celsius | 2 | | body\_temperature\_min | `apple` `health_connect` `fitbit` `oura` `polar` `withings` `samsung_health_data` `omron_us` `omron_eu` | Minimum body temperature | celsius | 2 | | breathing\_rate | `apple` `health_connect` `fitbit` `garmin` `oura` `polar` `whoop` `withings` `google_health` | Breathing rate | breaths/min | 4 | | breathing\_rate\_max | `apple` `health_connect` `fitbit` `garmin` `oura` `polar` `whoop` `withings` `google_health` | Maximum breathing rate | breaths/min | 4 | | breathing\_rate\_min | `apple` `health_connect` `fitbit` `garmin` `oura` `polar` `whoop` `withings` `google_health` | Minimum breathing rate | breaths/min | 4 | | cadence | `garmin` `polar` `strava` `suunto` `huawei` `coros` `samsung_health_data` | Average cadence | rpm | 4 | | cadence\_max | `garmin` `polar` `strava` `suunto` `huawei` `coros` `samsung_health_data` | Maximum cadence | rpm | 4 | | cadence\_min | `garmin` `polar` `strava` `suunto` `huawei` `coros` `samsung_health_data` | Minimum cadence | rpm | 4 | | calories\_burned | `apple` `health_connect` `fitbit` `oura` `strava` `suunto` `whoop` `huawei` `coros` `samsung_health_data` `google_health` | Total calories burned | kcal | 4 | | calories\_burned\_active | `apple` `health_connect` `fitbit` `garmin` `oura` `polar` `suunto` `whoop` `withings` `coros` `samsung_health_data` | Active calories burned | kcal | 4 | | calories\_burned\_basal | `apple` `health_connect` `fitbit` `garmin` `oura` `polar` `withings` | Basal calories burned | kcal | 4 | | calories\_intake | `huawei` | Calories intake | kcal | 4 | | descent | `garmin` `polar` `suunto` `huawei` `coros` | Total descent | meters | 2 | | distance | `apple` `health_connect` `fitbit` `garmin` `oura` `polar` `strava` `suunto` `whoop` `withings` `huawei` `coros` `samsung_health_data` `omron_us` `omron_eu` `google_health` | Total distance | meters | 4 | | distance\_cycling | `apple` `health_connect` `fitbit` `garmin` `oura` `polar` `strava` `suunto` `whoop` `coros` `samsung_health_data` | Cycling distance | meters | 4 | | distance\_running | `garmin` `oura` `polar` `strava` `suunto` `whoop` `coros` `samsung_health_data` | Running distance | meters | 4 | | distance\_swimming | `apple` `health_connect` `fitbit` `garmin` `polar` `strava` `whoop` `coros` `samsung_health_data` | Swimming distance | meters | 4 | | distance\_walking | `apple` `health_connect` `fitbit` `garmin` `oura` `polar` `strava` `suunto` `whoop` `coros` `samsung_health_data` | Walking distance | meters | 4 | | distance\_wheelchair | `apple` `health_connect` `fitbit` `garmin` `polar` `strava` `whoop` | Wheelchair distance | meters | 4 | | duration\_active | `fitbit` `garmin` `polar` `suunto` `huawei` `samsung_health_data` | Active duration | ms | 0 | | duration\_high\_intensity | `fitbit` `garmin` `oura` `withings` | Duration of high intensity | ms | 0 | | duration\_low\_intensity | `fitbit` `oura` `withings` | Duration of low intensity | ms | 0 | | duration\_moderate\_intensity | `fitbit` `garmin` `oura` `withings` `huawei` | Duration of moderate intensity | ms | 0 | | ecg\_rri | `polar` | ECG R-R interval | ms | 0 | | ecg\_voltage | `apple` `fitbit` `polar` `withings` `huawei` | ECG voltage | uV | 2 | | elevation | `apple` `fitbit` `garmin` `polar` `suunto` `withings` `huawei` `coros` `samsung_health_data` `google_health` | Elevation | meters | 2 | | elevation\_gain | `health_connect` `polar` `strava` `whoop` `huawei` `google_health` | Total elevation gain | meters | 2 | | elevation\_loss | `polar` `whoop` `huawei` | Total elevation loss | meters | 2 | | elevation\_max | `apple` `fitbit` `garmin` `polar` `strava` `suunto` `withings` `huawei` `coros` `samsung_health_data` `google_health` | Maximum elevation | meters | 2 | | elevation\_min | `apple` `fitbit` `garmin` `polar` `strava` `suunto` `withings` `huawei` `coros` `samsung_health_data` `google_health` | Minimum elevation | meters | 2 | | floors\_climbed | `apple` `health_connect` `fitbit` `garmin` `google_health` | Floors climbed | count | 0 | | glucose | `apple` `health_connect` `ultrahuman` `samsung_health_data` `dexcom` `freestyle_libre` `google_health` | Glucose | mg/dL | 6 | | heartrate | `apple` `health_connect` `fitbit` `garmin` `oura` `polar` `strava` `suunto` `whoop` `withings` `huawei` `ultrahuman` `coros` `samsung_health_data` `luna` `omron_us` `omron_eu` `google_health` | Heart rate | bpm | 0 | | heartrate\_max | `apple` `health_connect` `fitbit` `garmin` `oura` `polar` `strava` `suunto` `whoop` `withings` `huawei` `ultrahuman` `coros` `samsung_health_data` `luna` `omron_us` `omron_eu` `google_health` | Maximum heart rate | bpm | 0 | | heartrate\_min | `apple` `health_connect` `fitbit` `garmin` `oura` `polar` `strava` `suunto` `whoop` `withings` `huawei` `ultrahuman` `coros` `samsung_health_data` `luna` `omron_us` `omron_eu` `google_health` | Minimum heart rate | bpm | 0 | | heartrate\_resting | `apple` `health_connect` `fitbit` `garmin` `oura` `polar` `whoop` `huawei` `ultrahuman` `coros` `google_health` | Resting heart rate | bpm | 0 | | height | `apple` `health_connect` `oura` `polar` `whoop` `withings` `huawei` `samsung_health_data` `google_health` | Height | meters | 4 | | hrv\_rmssd | `health_connect` `fitbit` `garmin` `oura` `polar` `suunto` `whoop` `withings` `ultrahuman` `coros` `luna` `google_health` | Heart rate variability (RMSSD) | ms | 0 | | hrv\_sdnn | `apple` `withings` `google_health` | Heart rate variability (SDNN) | ms | 0 | | latitude | `apple` `fitbit` `garmin` `polar` `suunto` `samsung_health_data` `google_health` | Latitude | degrees | 8 | | longitude | `apple` `fitbit` `garmin` `polar` `suunto` `samsung_health_data` `google_health` | Longitude | degrees | 8 | | pace | `garmin` `suunto` `huawei` | Average pace | sec/m | 2 | | power | `samsung_health_data` | Power | w | 0 | | power\_max | `samsung_health_data` | Maximum power | w | 0 | | skin\_temperature | `apple` `health_connect` `polar` `whoop` `withings` `huawei` `ultrahuman` `samsung_health_data` `luna` `google_health` | Skin temperature | celsius | 2 | | skin\_temperature\_max | `apple` `health_connect` `polar` `whoop` `withings` `huawei` `ultrahuman` `samsung_health_data` `luna` `google_health` | Maximum skin temperature | celsius | 2 | | skin\_temperature\_min | `apple` `health_connect` `polar` `whoop` `withings` `huawei` `ultrahuman` `samsung_health_data` `luna` `google_health` | Minimum skin temperature | celsius | 2 | | sleep\_duration | `apple` `health_connect` `fitbit` `garmin` `oura` `polar` `suunto` `whoop` `withings` `huawei` `ultrahuman` `coros` `samsung_health_data` `luna` `google_health` | Total sleep duration | ms | 0 | | sleep\_duration\_awake | `apple` `health_connect` `fitbit` `garmin` `oura` `polar` `suunto` `whoop` `withings` `huawei` `ultrahuman` `samsung_health_data` `luna` `google_health` | Awake duration during sleep | ms | 0 | | sleep\_duration\_deep | `apple` `health_connect` `fitbit` `garmin` `oura` `polar` `suunto` `whoop` `withings` `huawei` `ultrahuman` `samsung_health_data` `luna` `google_health` | Deep sleep duration | ms | 0 | | sleep\_duration\_light | `apple` `health_connect` `fitbit` `garmin` `oura` `polar` `suunto` `whoop` `withings` `huawei` `ultrahuman` `samsung_health_data` `luna` `google_health` | Light sleep duration | ms | 0 | | sleep\_duration\_nap | `fitbit` `garmin` `oura` `suunto` `whoop` `luna` | Nap duration | ms | 0 | | sleep\_duration\_rem | `apple` `health_connect` `fitbit` `garmin` `oura` `polar` `suunto` `whoop` `withings` `huawei` `ultrahuman` `samsung_health_data` `luna` `google_health` | REM sleep duration | ms | 0 | | sleep\_efficiency | `oura` `polar` `whoop` `withings` `huawei` `ultrahuman` | Sleep efficiency | percentage | 0 | | sleep\_interruptions | `health_connect` `fitbit` `garmin` `oura` `whoop` `withings` `huawei` `samsung_health_data` `luna` `google_health` | Sleep interruptions | count | 0 | | sleep\_latency | `fitbit` `oura` `suunto` `withings` `huawei` `samsung_health_data` `luna` | Sleep latency | ms | 0 | | sleep\_skin\_temperature\_deviation | `fitbit` `garmin` `oura` `polar` | Skin temperature deviation from the baseline | celsius | 2 | | speed | `apple` `health_connect` `fitbit` `garmin` `polar` `strava` `suunto` `huawei` `coros` `samsung_health_data` | Average speed | m/sec | 4 | | speed\_max | `apple` `health_connect` `fitbit` `garmin` `polar` `strava` `suunto` `huawei` `coros` `samsung_health_data` | Maximum speed | m/sec | 4 | | speed\_min | `apple` `health_connect` `fitbit` `garmin` `polar` `strava` `suunto` `huawei` `coros` `samsung_health_data` | Minimum speed | m/sec | 4 | | spo2 | `apple` `health_connect` `fitbit` `garmin` `oura` `polar` `suunto` `whoop` `withings` `huawei` `ultrahuman` `samsung_health_data` `omron_us` `omron_eu` `google_health` | Blood oxygen level | percentage | 4 | | spo2\_max | `apple` `health_connect` `fitbit` `garmin` `oura` `polar` `suunto` `whoop` `withings` `huawei` `ultrahuman` `samsung_health_data` `omron_us` `omron_eu` `google_health` | Maximum blood oxygen level | percentage | 4 | | spo2\_min | `apple` `health_connect` `fitbit` `garmin` `oura` `polar` `suunto` `whoop` `withings` `huawei` `ultrahuman` `samsung_health_data` `omron_us` `omron_eu` `google_health` | Minimum blood oxygen level | percentage | 4 | | steps | `apple` `health_connect` `fitbit` `garmin` `oura` `polar` `suunto` `withings` `huawei` `ultrahuman` `coros` `samsung_health_data` `omron_us` `omron_eu` `google_health` | Total steps | count | 0 | | swimming\_distance\_per\_stroke | `garmin` `polar` `suunto` `coros` `samsung_health_data` | Swimming distance per stroke | meters | 2 | | swimming\_lengths | `garmin` `polar` `suunto` `coros` `samsung_health_data` | Swimming lengths | count | 0 | | vo2max | `apple` `health_connect` `fitbit` `garmin` `oura` `polar` `withings` `ultrahuman` `google_health` | VO2 max | mL/kg/min | 2 | | wakeup\_latency | `fitbit` `suunto` `samsung_health_data` | Wakeup latency | ms | 0 | | weight | `apple` `health_connect` `fitbit` `garmin` `oura` `polar` `whoop` `withings` `huawei` `samsung_health_data` `omron_us` `omron_eu` `google_health` | Weight | g | 4 | # Nutritional Fields Source: https://docs.spikeapi.com/technical-references/nutritional_fields The API supports analysis of 29 comprehensive nutritional fields, organized into macronutrients and micronutrients. ### Basic Macronutrients Always included unless specifically excluded: * **Energy** (`energy_kcal`) in kilocalories * **Carbohydrates** (`carbohydrate_g`) in grams * **Protein** (`protein_g`) in grams * **Total Fat** (`fat_total_g`) in grams ### Fat Breakdown * **Saturated Fat** (`fat_saturated_g`) in grams * **Polyunsaturated Fat** (`fat_polyunsaturated_g`) in grams (micronutrient) * **Monounsaturated Fat** (`fat_monounsaturated_g`) in grams (micronutrient) * **Trans Fat** (`fat_trans_g`) in grams ### Additional Macronutrient Details * **Total Sugars** (`sugars_total_g`) in grams * **Dietary Fiber** (`fiber_total_dietary_g`) in grams (micronutrient) * **Cholesterol** (`cholesterol_mg`) in milligrams (micronutrient) ### Essential Minerals * **Sodium** (`sodium_mg`) in milligrams * **Potassium** (`potassium_mg`) in milligrams * **Calcium** (`calcium_mg`) in milligrams * **Iron** (`iron_mg`) in milligrams * **Magnesium** (`magnesium_mg`) in milligrams * **Phosphorus** (`phosphorus_mg`) in milligrams * **Zinc** (`zinc_mg`) in milligrams ### Vitamins * **Vitamin A** (`vitamin_a_rae_mcg`) in micrograms RAE * **Vitamin C** (`vitamin_c_mg`) in milligrams * **Vitamin D** (`vitamin_d_mcg`) in micrograms * **Vitamin E** (`vitamin_e_mg`) in milligrams * **Vitamin K** (`vitamin_k_mcg`) in micrograms ### B-Complex Vitamins * **Thiamin (B1)** (`thiamin_mg`) in milligrams * **Riboflavin (B2)** (`riboflavin_mg`) in milligrams * **Niacin (B3)** (`niacin_mg`) in milligrams * **Vitamin B6** (`vitamin_b6_mg`) in milligrams * **Folate** (`folate_mcg`) in micrograms * **Vitamin B12** (`vitamin_b12_mcg`) in micrograms # Supported Providers Source: https://docs.spikeapi.com/technical-references/provider_matrix This document provides a comprehensive overview of all providers, detailing their capabilities. The list below summarizes the key features supported by each provider. ## Apple HealthKit **Slug: `apple`** ### Intraday Metrics `blood_pressure_diastolic` `blood_pressure_diastolic_max` `blood_pressure_diastolic_min` `blood_pressure_systolic` `blood_pressure_systolic_max` `blood_pressure_systolic_min` `body_fat` `body_fat_max` `body_fat_min` `body_mass_index` `body_temperature` `body_temperature_max` `body_temperature_min` `breathing_rate` `breathing_rate_max` `breathing_rate_min` `calories_burned` `calories_burned_active` `calories_burned_basal` `distance` `distance_cycling` `distance_swimming` `distance_walking` `distance_wheelchair` `floors_climbed` `glucose` `heartrate` `heartrate_max` `heartrate_min` `heartrate_resting` `hrv_sdnn` `skin_temperature` `skin_temperature_max` `skin_temperature_min` `speed` `speed_max` `speed_min` `spo2` `spo2_max` `spo2_min` `steps` `vo2max` `weight` `ecg_voltage` ### Daily Metrics `calories_burned` `calories_burned_active` `calories_burned_basal` `distance` `distance_cycling` `distance_wheelchair` `heartrate` `heartrate_max` `heartrate_min` `heartrate_resting` `hrv_sdnn` `steps` ### Sleep Metrics `breathing_rate` `breathing_rate_max` `breathing_rate_min` `heartrate` `heartrate_max` `heartrate_min` `hrv_sdnn` `skin_temperature` `skin_temperature_max` `skin_temperature_min` `sleep_duration` `sleep_duration_awake` `sleep_duration_deep` `sleep_duration_light` `sleep_duration_rem` `spo2` `spo2_max` `spo2_min` ### Workout Metrics `calories_burned` `calories_burned_active` `calories_burned_basal` `distance` `distance_cycling` `distance_swimming` `distance_walking` `distance_wheelchair` `elevation` `elevation_max` `elevation_min` `floors_climbed` `heartrate` `heartrate_max` `heartrate_min` `hrv_sdnn` `latitude` `longitude` `speed` `speed_max` `speed_min` `spo2` `spo2_max` `spo2_min` `steps` `vo2max` ### User Properties `body_fat` `body_mass_index` `height` `weight` `body_fat_min` `body_fat_max` `birth_date` `gender` ### Backfill Limitations Manual implementation required. Limited by device data, permissions, and user settings. Refer to iOS SDK documentation. *** ## Coros **Slug: `coros`** ### Daily Metrics `calories_burned` `heartrate_resting` `steps` ### Sleep Metrics `heartrate` `hrv_rmssd` `sleep_duration` `heartrate_max` `heartrate_min` ### Workout Metrics `calories_burned` `distance` `distance_cycling` `distance_running` `distance_swimming` `distance_walking` `heartrate` `speed` `steps` `heartrate_max` `heartrate_min` `speed_max` `speed_min` `ascent` `cadence` `cadence_max` `cadence_min` `calories_burned_active` `descent` `elevation` `elevation_max` `elevation_min` `swimming_distance_per_stroke` `swimming_lengths` *** ## Dexcom **Slug: `dexcom`** ### Intraday Metrics `glucose` *** ## Fitbit **Slug: `fitbit`** ### Intraday Metrics `heartrate` `heartrate_max` `heartrate_min` `steps` `distance` `calories_burned` `calories_burned_basal` `hrv_rmssd` `spo2` `spo2_min` `spo2_max` `body_temperature` `body_temperature_max` `body_temperature_min` `ecg_voltage` ### Daily Metrics `calories_burned` `calories_burned_active` `calories_burned_basal` `duration_active` `duration_low_intensity` `duration_moderate_intensity` `duration_high_intensity` `floors_climbed` `heartrate_resting` `steps` `hrv_rmssd` `vo2max` `breathing_rate` `breathing_rate_min` `breathing_rate_max` `sleep_skin_temperature_deviation` ### Sleep Metrics `bedtime_duration` `sleep_duration` `sleep_duration_awake` `sleep_duration_deep` `sleep_duration_light` `sleep_duration_nap` `sleep_duration_rem` `sleep_interruptions` `sleep_latency` `wakeup_latency` ### Workout Metrics `calories_burned_active` `distance` `distance_cycling` `distance_swimming` `distance_walking` `distance_wheelchair` `elevation` `elevation_max` `elevation_min` `heartrate` `latitude` `longitude` `speed` `steps` `heartrate_max` `heartrate_min` `speed_max` `speed_min` ### User Properties `body_fat` `body_fat_min` `body_fat_max` `body_mass_index` `weight` ### Backfill Limitations Up to 90 days available. Activities and heart rate time series limited to 30 days. Automatic backfill may take minutes to hours. *** ## Freestyle Libre **Slug: `freestyle_libre`** ### Intraday Metrics `glucose` *** ## Garmin **Slug: `garmin`** ### Intraday Metrics `calories_burned_active` `distance` `steps` `blood_pressure_systolic` `blood_pressure_diastolic` `heartrate` `heartrate_max` `heartrate_min` `blood_pressure_systolic_min` `blood_pressure_systolic_max` `blood_pressure_diastolic_min` `blood_pressure_diastolic_max` ### Daily Metrics `calories_burned_active` `calories_burned_basal` `distance` `distance_cycling` `distance_running` `distance_swimming` `distance_walking` `distance_wheelchair` `duration_active` `duration_moderate_intensity` `duration_high_intensity` `floors_climbed` `heartrate` `heartrate_max` `heartrate_min` `heartrate_resting` `steps` `hrv_rmssd` `vo2max` `sleep_skin_temperature_deviation` ### Sleep Metrics `breathing_rate` `breathing_rate_max` `breathing_rate_min` `sleep_duration` `sleep_duration_awake` `sleep_duration_deep` `sleep_duration_light` `sleep_duration_nap` `sleep_duration_rem` `sleep_interruptions` `spo2` `spo2_max` `spo2_min` ### Workout Metrics `ascent` `cadence` `cadence_max` `calories_burned_active` `descent` `distance_cycling` `distance_running` `distance_swimming` `distance_walking` `distance_wheelchair` `heartrate` `heartrate_max` `latitude` `longitude` `pace` `speed` `speed_max` `steps` `swimming_lengths` `heartrate_min` `speed_min` `cadence_min` `distance` `duration_active` `elevation` `elevation_max` `elevation_min` `floors_climbed` `floors_climbed_max` `spo2` `spo2_max` `spo2_min` `swimming_distance_per_stroke` ### User Properties `body_mass_index` `weight` `body_fat` `body_bone_mass` `body_fat_min` `body_fat_max` ### Backfill Limitations Up to 90 days available. One backfill per user per application. May take up to 12 hours. *** ## Google Health (beta) **Slug: `google_health`** ### Intraday Metrics `distance` `floors_climbed` `steps` `body_fat` `glucose` `heartrate` `hrv_rmssd` `hrv_sdnn` `spo2` `vo2max` `weight` `heartrate_max` `heartrate_min` `spo2_max` `spo2_min` `body_fat_min` `body_fat_max` ### Daily Metrics `breathing_rate` `heartrate_resting` `skin_temperature` `spo2` `spo2_max` `spo2_min` `breathing_rate_min` `breathing_rate_max` `skin_temperature_max` `skin_temperature_min` ### Sleep Metrics `bedtime_duration` `sleep_duration` `sleep_duration_awake` `sleep_duration_deep` `sleep_duration_light` `sleep_duration_rem` `sleep_interruptions` ### Workout Metrics `calories_burned` `distance` `elevation_gain` `heartrate` `steps` `heartrate_max` `heartrate_min` `elevation` `latitude` `longitude` `elevation_max` `elevation_min` ### User Properties `body_fat` `height` `weight` `body_fat_min` `body_fat_max` *** ## Google Health Connect **Slug: `health_connect`** ### Intraday Metrics `blood_pressure_diastolic` `blood_pressure_diastolic_max` `blood_pressure_diastolic_min` `blood_pressure_systolic` `blood_pressure_systolic_max` `blood_pressure_systolic_min` `body_temperature` `body_temperature_max` `body_temperature_min` `breathing_rate` `breathing_rate_max` `breathing_rate_min` `calories_burned` `calories_burned_active` `calories_burned_basal` `distance` `elevation_gain` `floors_climbed` `glucose` `heartrate` `heartrate_max` `heartrate_min` `heartrate_resting` `hrv_rmssd` `skin_temperature` `skin_temperature_max` `skin_temperature_min` `spo2` `spo2_max` `spo2_min` `speed` `speed_max` `speed_min` `steps` `vo2max` `weight` ### Daily Metrics `calories_burned` `calories_burned_active` `calories_burned_basal` `distance` `heartrate` `heartrate_max` `heartrate_min` `heartrate_resting` `steps` ### Sleep Metrics `heartrate` `heartrate_max` `heartrate_min` `sleep_duration` `sleep_duration_awake` `sleep_duration_deep` `sleep_duration_light` `sleep_duration_rem` `sleep_interruptions` ### Workout Metrics `calories_burned` `calories_burned_active` `calories_burned_basal` `distance` `distance_cycling` `distance_swimming` `distance_walking` `distance_wheelchair` `heartrate` `heartrate_max` `heartrate_min` `spo2` `spo2_max` `spo2_min` `speed` `speed_max` `speed_min` `steps` `vo2max` ### User Properties `body_fat` `body_mass_index` `bone_mass` `height` `weight` `body_fat_min` `body_fat_max` ### Backfill Limitations Manual implementation required. Limited by device data and permissions. Up to 30 days predating permission grant. Refer to Android SDK documentation. *** ## Huawei **Slug: `huawei`** ### Intraday Metrics `blood_pressure_diastolic` `blood_pressure_systolic` `calories_burned` `distance` `heartrate` `heartrate_max` `heartrate_min` `heartrate_resting` `skin_temperature` `spo2` `steps` `spo2_max` `spo2_min` `skin_temperature_max` `skin_temperature_min` `blood_pressure_systolic_min` `blood_pressure_systolic_max` `blood_pressure_diastolic_min` `blood_pressure_diastolic_max` `ecg_voltage` `speed` `speed_max` `speed_min` ### Daily Metrics `duration_active` `steps` `calories_intake` `distance` `duration_moderate_intensity` ### Sleep Metrics `bedtime_duration` `sleep_duration` `sleep_duration_awake` `sleep_duration_deep` `sleep_duration_light` `sleep_duration_rem` `sleep_efficiency` `sleep_interruptions` `sleep_latency` ### Workout Metrics `ascent` `cadence` `cadence_max` `cadence_min` `calories_intake` `descent` `distance` `duration_active` `elevation` `elevation_gain` `elevation_loss` `elevation_max` `elevation_min` `heartrate` `heartrate_max` `heartrate_min` `pace` `speed` `speed_max` `speed_min` `steps` ### User Properties `weight` `body_fat` `body_fat_min` `body_fat_max` `height` *** ## Luna **Slug: `luna`** ### Intraday Metrics `heartrate` `hrv_rmssd` `heartrate_max` `heartrate_min` ### Daily Metrics `heartrate` `heartrate_max` `heartrate_min` `hrv_rmssd` ### Sleep Metrics `bedtime_duration` `skin_temperature` `sleep_duration` `sleep_duration_awake` `sleep_duration_deep` `sleep_duration_light` `sleep_duration_nap` `sleep_duration_rem` `sleep_interruptions` `sleep_latency` `skin_temperature_max` `skin_temperature_min` *** ## Omron EU **Slug: `omron_eu`** ### Intraday Metrics `blood_pressure_diastolic` `blood_pressure_systolic` `heartrate` `heartrate_max` `heartrate_min` `blood_pressure_systolic_min` `blood_pressure_systolic_max` `blood_pressure_diastolic_min` `blood_pressure_diastolic_max` `calories` `distance` `steps` `body_fat` `body_mass_index` `weight` `body_fat_min` `body_fat_max` `body_temperature` `body_temperature_max` `body_temperature_min` `spo2` `spo2_max` `spo2_min` ### User Properties `body_fat` `body_mass_index` `weight` `body_fat_min` `body_fat_max` *** ## Omron US **Slug: `omron_us`** ### Intraday Metrics `blood_pressure_diastolic` `blood_pressure_systolic` `heartrate` `heartrate_max` `heartrate_min` `blood_pressure_systolic_min` `blood_pressure_systolic_max` `blood_pressure_diastolic_min` `blood_pressure_diastolic_max` `calories` `distance` `steps` `body_fat` `body_mass_index` `weight` `body_fat_min` `body_fat_max` `body_temperature` `body_temperature_max` `body_temperature_min` `spo2` `spo2_max` `spo2_min` ### User Properties `body_fat` `body_mass_index` `weight` `body_fat_min` `body_fat_max` *** ## Oura **Slug: `oura`** ### Intraday Metrics `heartrate` `heartrate_max` `heartrate_min` ### Daily Metrics `calories_burned` `calories_burned_active` `calories_burned_basal` `duration_low_intensity` `duration_moderate_intensity` `duration_high_intensity` `steps` `spo2` `spo2_max` `spo2_min` `vo2max` ### Sleep Metrics `bedtime_duration` `body_temperature` `breathing_rate` `heartrate` `heartrate_min` `heartrate_resting` `hrv_rmssd` `sleep_duration` `sleep_duration_awake` `sleep_duration_deep` `sleep_duration_light` `sleep_duration_nap` `sleep_duration_rem` `sleep_efficiency` `sleep_interruptions` `sleep_latency` `sleep_skin_temperature_deviation` `heartrate_max` `breathing_rate_min` `breathing_rate_max` `body_temperature_max` `body_temperature_min` ### Workout Metrics `calories_burned` `distance` `distance_cycling` `distance_running` `distance_walking` `heartrate` `heartrate_max` `heartrate_min` `hrv_rmssd` ### User Properties `height` `weight` `gender` `age` ### Backfill Limitations Up to 180 days available. Heart rate data limited to 30 days. Recommend 30 days or less. *** ## Polar **Slug: `polar`** ### Intraday Metrics `heartrate` `heartrate_max` `heartrate_min` `hrv_rmssd` `spo2` `spo2_max` `spo2_min` `body_temperature` `body_temperature_max` `body_temperature_min` `height` `heartrate_resting` `vo2max` `ecg_rri` `ecg_voltage` ### Daily Metrics `calories_burned_active` `calories_burned_basal` `steps` `breathing_rate` `breathing_rate_max` `breathing_rate_min` `heartrate` `hrv_rmssd` `heartrate_max` `heartrate_min` `skin_temperature` `sleep_skin_temperature_deviation` `skin_temperature_max` `skin_temperature_min` `vo2max` `heartrate_resting` ### Sleep Metrics `heartrate` `heartrate_max` `heartrate_min` `sleep_duration` `sleep_duration_awake` `sleep_duration_deep` `sleep_duration_light` `sleep_duration_rem` `sleep_efficiency` ### Workout Metrics `ascent` `calories_burned_active` `descent` `distance` `distance_cycling` `distance_running` `distance_swimming` `distance_walking` `distance_wheelchair` `elevation` `elevation_gain` `elevation_loss` `elevation_max` `elevation_min` `heartrate` `heartrate_max` `heartrate_min` `speed` `speed_max` `speed_min` `cadence` `cadence_max` `cadence_min` `duration_active` `latitude` `longitude` `steps` `swimming_distance_per_stroke` `swimming_lengths` ### User Properties `height` `weight` ### Backfill Limitations Up to 28 days available. *** ## Samsung Health Data **Slug: `samsung_health_data`** ### Intraday Metrics `calories_burned` `calories_burned_active` `distance` `glucose` `steps` `blood_pressure_diastolic` `blood_pressure_systolic` `body_temperature` `heartrate` `heartrate_max` `heartrate_min` `skin_temperature` `skin_temperature_max` `skin_temperature_min` `spo2` `spo2_max` `spo2_min` `body_temperature_max` `body_temperature_min` `blood_pressure_systolic_min` `blood_pressure_systolic_max` `blood_pressure_diastolic_min` `blood_pressure_diastolic_max` ### Sleep Metrics `bedtime_duration` `skin_temperature` `skin_temperature_max` `skin_temperature_min` `sleep_duration` `sleep_duration_awake` `sleep_duration_deep` `sleep_duration_light` `sleep_duration_rem` `sleep_interruptions` `sleep_latency` `wakeup_latency` ### Workout Metrics `cadence` `cadence_max` `cadence_min` `calories_burned` `calories_burned_active` `distance` `distance_cycling` `distance_running` `distance_swimming` `distance_walking` `duration_active` `elevation` `elevation_max` `elevation_min` `heartrate` `heartrate_max` `heartrate_min` `latitude` `longitude` `power` `power_max` `speed` `speed_max` `speed_min` `spo2` `spo2_max` `spo2_min` `steps` `swimming_distance_per_stroke` `swimming_lengths` ### User Properties `body_fat` `body_mass_index` `height` `weight` `body_fat_min` `body_fat_max` `birth_date` `birth_year` `gender` *** ## Strava **Slug: `strava`** ### Workout Metrics `cadence` `calories_burned` `distance` `distance_cycling` `distance_running` `distance_swimming` `distance_walking` `distance_wheelchair` `elevation_gain` `elevation_max` `elevation_min` `heartrate` `heartrate_max` `speed` `speed_max` `heartrate_min` `speed_min` `cadence_min` `cadence_max` *** ## Suunto **Slug: `suunto`** ### Intraday Metrics `calories_burned_active` `elevation` `heartrate` `heartrate_max` `heartrate_min` `hrv_rmssd` `spo2` `steps` `elevation_max` `elevation_min` `spo2_max` `spo2_min` ### Daily Metrics `calories_burned_active` `steps` ### Sleep Metrics `heartrate` `heartrate_min` `hrv_rmssd` `sleep_duration` `sleep_duration_awake` `sleep_duration_deep` `sleep_duration_light` `sleep_duration_nap` `sleep_duration_rem` `sleep_latency` `spo2` `wakeup_latency` `heartrate_max` `spo2_max` `spo2_min` ### Workout Metrics `ascent` `cadence` `cadence_max` `cadence_min` `calories_burned` `descent` `distance` `elevation_max` `elevation_min` `heartrate` `heartrate_max` `latitude` `longitude` `pace` `speed` `speed_max` `steps` `heartrate_min` `speed_min` `calories_burned_active` `distance_cycling` `distance_running` `distance_walking` `duration_active` `elevation` `swimming_distance_per_stroke` `swimming_lengths` ### Backfill Limitations Up to 28 days available. *** ## Ultrahuman **Slug: `ultrahuman`** ### Intraday Metrics `heartrate` `heartrate_max` `heartrate_min` `hrv_rmssd` `skin_temperature` `skin_temperature_max` `skin_temperature_min` `steps` `glucose` ### Daily Metrics `heartrate` `heartrate_max` `heartrate_min` `hrv_rmssd` `skin_temperature` `skin_temperature_min` `skin_temperature_max` `heartrate_resting` `steps` `vo2max` ### Sleep Metrics `bedtime_duration` `heartrate` `heartrate_max` `heartrate_min` `hrv_rmssd` `skin_temperature` `skin_temperature_max` `skin_temperature_min` `sleep_duration` `sleep_duration_awake` `sleep_duration_deep` `sleep_duration_light` `sleep_duration_rem` `sleep_efficiency` `spo2` `spo2_max` `spo2_min` *** ## Whoop **Slug: `whoop`** ### Daily Metrics `calories_burned` `heartrate` `heartrate_max` `heartrate_min` `heartrate_resting` `hrv_rmssd` `skin_temperature` `spo2` `spo2_max` `spo2_min` `skin_temperature_max` `skin_temperature_min` ### Sleep Metrics `breathing_rate` `sleep_duration` `sleep_duration_awake` `sleep_duration_deep` `sleep_duration_light` `sleep_duration_nap` `sleep_duration_rem` `sleep_efficiency` `sleep_interruptions` `breathing_rate_min` `breathing_rate_max` ### Workout Metrics `calories_burned_active` `distance` `distance_cycling` `distance_running` `distance_swimming` `distance_walking` `distance_wheelchair` `elevation_gain` `elevation_loss` `heartrate` `heartrate_max` `heartrate_min` ### User Properties `height` `weight` ### Backfill Limitations Up to 180 days available. Recommend 30 days or less for consistency. *** ## Withings **Slug: `withings`** ### Intraday Metrics `blood_pressure_diastolic` `blood_pressure_systolic` `body_temperature` `heartrate` `skin_temperature` `spo2` `vo2max` `heartrate_max` `heartrate_min` `spo2_max` `spo2_min` `body_temperature_max` `body_temperature_min` `skin_temperature_max` `skin_temperature_min` `blood_pressure_systolic_min` `blood_pressure_systolic_max` `blood_pressure_diastolic_min` `blood_pressure_diastolic_max` `calories_burned_active` `distance` `elevation` `elevation_max` `elevation_min` `steps` `ecg_voltage` ### Daily Metrics `calories_burned_active` `calories_burned_basal` `distance` `duration_low_intensity` `duration_moderate_intensity` `duration_high_intensity` `elevation` `heartrate` `heartrate_max` `heartrate_min` `steps` `elevation_max` `elevation_min` ### Sleep Metrics `bedtime_duration` `breathing_rate` `breathing_rate_max` `breathing_rate_min` `heartrate` `heartrate_max` `heartrate_min` `sleep_duration` `sleep_duration_awake` `sleep_duration_deep` `sleep_duration_light` `sleep_duration_rem` `sleep_efficiency` `sleep_latency` `hrv_rmssd` `hrv_sdnn` `sleep_interruptions` ### Workout Metrics `calories_burned_active` `distance` `elevation` `heartrate` `heartrate_max` `heartrate_min` `spo2` `steps` `elevation_max` `elevation_min` `spo2_max` `spo2_min` ### User Properties `body_bone_mass` `body_fat` `height` `weight` `body_fat_min` `body_fat_max` ### Backfill Limitations Up to 90 days available. Recommend 30 days or less for consistency. *** # Statistic Types and Methods Source: https://docs.spikeapi.com/technical-references/statistics_matrix This document provides a comprehensive overview of all statistics, detailing their methods, metrics, units, precision, and descriptions. The table below summarizes the key features supported by each statistic. ## Statistics The following table lists all the statistics available, along with their descriptions, applicable methods, associated metrics, units of measurement, and number of decimals for output precision. | **Label** | **Description** | **Methods** | **Metrics** | **Unit** | **Precision** | | :---------------------------------- | :----------------------------------------------------------------- | :---------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------- | :------------ | | calories\_burned\_active | Calories burned during active periods | intersect\_sum | `calories_burned_active` | kcal | 4 | | calories\_burned\_basal | Calories burned at rest (basal metabolic rate) | intersect\_sum | `calories_burned_basal` | kcal | 4 | | calories\_burned\_total | Total calories burned including basal and active periods | intersect\_union\_sum | `calories_burned` `calories_burned_basal` `calories_burned_active` | kcal | 4 | | distance\_cycling | Total distance covered while cycling | intersect\_sum | `distance_cycling` | meters | 4 | | distance\_running | Total distance covered while running | intersect\_sum | `distance_running` | meters | 4 | | distance\_total | Total distance covered across all activities | intersect\_union\_sum | `distance` `distance_walking` `distance_cycling` `distance_running` | meters | 4 | | distance\_walking | Total distance covered while walking | intersect\_sum | `distance_walking` | meters | 4 | | duration\_high\_intensity | Duration of high level intensity | intersect\_sum | `duration_high_intensity` | ms | 0 | | duration\_low\_intensity | Duration of low level intensity | intersect\_sum | `duration_low_intensity` | ms | 0 | | duration\_moderate\_intensity | Duration of medium level intensity | intersect\_sum | `duration_moderate_intensity` | ms | 0 | | heartrate | Average heart rate over a period | duration\_weighted\_avg | `heartrate` | bpm | 0 | | heartrate\_max | Maximum recorded heart rate, providing insights into peak values | max | `heartrate` | bpm | 0 | | heartrate\_min | Minimum recorded heart rate, providing insights into lowest values | min | `heartrate` | bpm | 0 | | heartrate\_resting | Average resting heart rate | duration\_weighted\_avg | `heartrate_resting` | bpm | 0 | | hrv\_rmssd | Heart rate variability measured by RMSSD | duration\_weighted\_avg | `hrv_rmssd` | ms | 0 | | hrv\_sdnn | Heart rate variability measured by SDNN | duration\_weighted\_avg | `hrv_sdnn` | ms | 0 | | sleep\_duration\_total | Total sleep duration across all sleep stages | intersect\_union\_sum | `sleep_duration_deep` `sleep_duration_light` `sleep_duration_rem` `sleep_duration_awake` | ms | 0 | | sleep\_score | Overall sleep score based on various sleep metrics | sleep\_score | `bedtime_duration` `sleep_duration` `sleep_duration_nap` `sleep_duration_light` `sleep_duration_deep` `sleep_duration_rem` `sleep_duration_awake` `sleep_interruptions` `sleep_efficiency` `sleep_latency` `sleep_breathing_rate` | count | 0 | | sleep\_skin\_temperature\_deviation | Sleep skin temperature deviation from the baseline | intersect\_sum | `sleep_skin_temperature_deviation` | celsius | 2 | | steps | Total number of steps taken | intersect\_sum | `steps` | count | 0 | ## Methods The table below provides details on the various methods used in the statistics, including a brief description of each. | **Label** | **Description** | | :---------------------- | :------------------------------------------------------------------------------------------------------------------------------- | | duration\_weighted\_avg | Average of values weighted by their duration, ensuring longer durations have a greater impact on the average. | | intersect\_sum | Sum of intersecting values of specified metrics, aggregating overlapping data points to provide a total count or sum. | | intersect\_union\_sum | Sum of the union of intersecting values, merging several metrics to provide a comprehensive aggregation across multiple metrics. | | max | Maximum value of the specified metrics, providing insights into the peak values recorded. | | min | Minimum value of the specified metrics, providing insights into the lowest values recorded. | | sleep\_score | A score representing the quality of sleep, calculated using intersecting values. |