Meal Tracking
Learn how to track meals and food items to award points for sustainable food choices.
Overview
Food tracking enables users to earn points by logging their meals and food consumption. When a user tracks food through your app, you send the data to Vela for health and environmental impact rating, and points are awarded based on the nutritional and environmental impact.
There are two tracking endpoints:
- Meal Tracking (
/track/meals) - For logging meals with multiple ingredients - Food Tracking (
/track/food) - For logging single food items
| Scenario | Endpoint |
|---|---|
| Single snack or packaged product | /track/food |
| Recipe or homemade meal | /track/meals |
| Packaged combo meal | /track/meals |
| Multiple items from different merchants | /track/meals |
Both endpoints accept data asynchronously and return results via webhooks.
Meal Tracking
Use this endpoint when users log a meal containing multiple food items.
Endpoint: POST /track/meals
Headers:
| Header | Value |
|---|---|
Content-Type | application/json |
x-client-id | Your client ID |
x-client-secret | Your client secret |
Query Parameters:
| Parameter | Required | Description |
|---|---|---|
api-version | Yes | API version (e.g., 1.0) |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
userId | String | Yes | User's Vela ID |
transactionId | String | Yes | Your unique transaction reference |
mealName | String | Yes | Name of the meal |
imageUrl | String | Yes | Image URL of the meal |
loggedAt | String (ISO 8601) | Yes | When the meal was logged |
mealType | String | Yes | breakfast, lunch, dinner, snack, or other |
country | String | No | ISO country code |
location | String | No | Free-text location |
metadata | Object | No | Optional metadata |
ingredients | Array | No | Array of ingredient objects |
Ingredient Object
| Field | Type | Required | Description |
|---|---|---|---|
foodId | String | Yes | Unique ID of the food item |
name | String | Yes | Name of the food item |
imageUrl | String | No | Image URL of the food |
merchant | Object | No | Merchant details |
weight | Number | No | Weight of the food |
unit | String | No | Unit of measure (e.g., g, ml) |
quantity | Number | No | Quantity (default: 1) |
amount | Number | No | Monetary value (default: £1) |
currency | String | No | Currency code (required if amount provided) |
nutrition | Object | No | Nutritional information |
Merchant Object
| Field | Type | Required | Description |
|---|---|---|---|
name | String | No | Merchant name |
logo | String | No | Logo URL |
website | String | No | Merchant website |
domain | String | No | Merchant domain |
country | String | No | Merchant country |
Nutrition Object
| Field | Type | Description |
|---|---|---|
energyKcal | Number | Calories |
proteinG | Number | Protein in grams |
carbsG | Number | Carbohydrates in grams |
fatG | Number | Fat in grams |
sugarG | Number | Sugar in grams |
fiberG | Number | Fiber in grams |
saltG | Number | Salt in grams |
Example Request
{
"userId": "08FE9Q",
"transactionId": "meal-20251126-002",
"mealName": "Avocado Quinoa Bowl",
"imageUrl": "https://example.com/images/meals/avocado-quinoa-bowl.jpg",
"loggedAt": "2025-11-26T19:40:00Z",
"mealType": "dinner",
"country": "GBR",
"location": "London, UK",
"metadata": {
"source": "mobile_app",
"appVersion": "4.2.1"
},
"ingredients": [
{
"foodId": "FOOD-221",
"name": "Quinoa",
"imageUrl": "https://example.com/images/foods/quinoa.jpg",
"merchant": {
"name": "Whole Foods",
"domain": "wholefoods.com",
"country": "GBR"
},
"weight": 150,
"unit": "g",
"quantity": 1,
"amount": 2.80,
"currency": "GBP",
"nutrition": {
"energyKcal": 110,
"proteinG": 4,
"carbsG": 20,
"fatG": 2,
"sugarG": 0,
"fiberG": 3,
"saltG": 0
}
},
{
"foodId": "FOOD-874",
"name": "Avocado",
"imageUrl": "https://example.com/images/foods/avocado.jpg",
"merchant": {
"name": "Local Market",
"country": "GBR"
},
"weight": 100,
"unit": "g",
"quantity": 1,
"amount": 3.40,
"currency": "GBP",
"nutrition": {
"energyKcal": 160,
"proteinG": 2,
"carbsG": 9,
"fatG": 15,
"sugarG": 0,
"fiberG": 7,
"saltG": 0
}
}
]
}Response
{
"trackId": "5765279d-0e9b-4f16-b462-0f75d06d0c7a",
"message": "Meal accepted for scoring"
}| Field | Type | Description |
|---|---|---|
trackId | String | Vela's internal tracking ID |
message | String | Confirmation message |
Response Codes
| Code | Description |
|---|---|
202 | Meal accepted for processing |
400 | Invalid request parameters |
500 | Internal server error |
Food Tracking
Use this endpoint when users log a single food item.
Endpoint: POST /track/food
Headers:
| Header | Value |
|---|---|
Content-Type | application/json |
x-client-id | Your client ID |
x-client-secret | Your client secret |
Query Parameters:
| Parameter | Required | Description |
|---|---|---|
api-version | Yes | API version (e.g., 1.0) |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
userId | String | Yes | User's Vela ID |
transactionId | String | Yes | Your unique transaction reference |
foodId | String | Yes | Unique ID of the food item |
name | String | Yes | Name of the food item |
imageUrl | String | Yes | Image URL of the food |
merchant | Object | Yes | Merchant details |
weight | Number | Yes | Weight of the food |
unit | String | Yes | Unit of measure |
nutrition | Object | Yes | Nutritional information |
quantity | Number | No | Quantity (default: 1) |
amount | Number | No | Monetary value (default: £1) |
currency | String | No | Currency code |
Example Request
{
"userId": "08FE9Q",
"transactionId": "food-20251126-038",
"foodId": "FOOD-44521",
"name": "Grilled Chicken Quinoa Bowl",
"imageUrl": "https://example.com/foods/grilled-chicken-quinoa.jpg",
"merchant": {
"name": "Health Kitchen",
"logo": "https://example.com/logos/health-kitchen.png",
"website": "https://www.healthkitchen.co.uk",
"domain": "healthkitchen.co.uk",
"country": "GBR"
},
"weight": 420,
"unit": "g",
"quantity": 1,
"amount": 8.40,
"currency": "GBP",
"nutrition": {
"energyKcal": 610,
"proteinG": 38,
"carbsG": 54,
"fatG": 22,
"sugarG": 5,
"fiberG": 8,
"saltG": 1.1
}
}Response
{
"trackId": "5765279d-0e9b-4f16-b462-0f75d06d0c7a",
"message": "Food accepted for scoring"
}Response Codes
| Code | Description |
|---|---|
202 | Food accepted for processing |
400 | Invalid request parameters |
500 | Internal server error |
Webhook Events
After submitting a meal or food item, Vela processes the data asynchronously and sends webhook notifications as processing progresses.
| Event | Description |
|---|---|
intake.received | Meal/food payload accepted for processing |
intake.scored | Health and environmental impact metrics calculated |
intake.completed | Points awarded |
intake.failed | Processing failed |
intake.received
Sent when Vela receives and queues the submission for processing.
{
"event": "intake.received",
"source": "meal",
"transactionId": "meal-20251126-002",
"trackId": "5765279d-0e9b-4f16-b462-0f75d06d0c7a",
"userId": "08FE9Q",
"loggedAt": "2025-11-26T19:45:00Z",
"country": "GBR",
"createdAt": "2025-11-26T19:45:02Z"
}intake.scored
Sent when health and environmental impact metrics have been calculated.
{
"event": "intake.scored",
"source": "meal",
"transactionId": "meal-20251126-002",
"trackId": "5765279d-0e9b-4f16-b462-0f75d06d0c7a",
"name": "Avocado Quinoa Bowl",
"imageUrl": "https://example.com/images/meals/avocado-quinoa-bowl.jpg",
"userId": "08FE9Q",
"mealType": "dinner",
"ingredients": [
{
"foodId": "FOOD-221",
"name": "Quinoa",
"imageUrl": "https://example.com/images/foods/quinoa.jpg",
"weight": 150,
"unit": "g",
"quantity": 1,
"amount": 2.80,
"currency": "GBP",
"carbonImpactRating": 2,
"healthImpactRating": 1,
"co2e": 0.45
},
{
"foodId": "FOOD-874",
"name": "Avocado",
"imageUrl": "https://example.com/images/foods/avocado.jpg",
"weight": 100,
"unit": "g",
"quantity": 1,
"amount": 3.40,
"currency": "GBP",
"carbonImpactRating": 3,
"healthImpactRating": 2,
"co2e": 0.68
}
],
"loggedAt": "2025-11-26T19:40:00Z",
"country": "GBR",
"totalAmount": 6.20,
"currency": "GBP",
"totalCo2e": 1.13,
"carbonImpactRating": 2,
"healthImpactRating": 1,
"createdAt": "2025-11-26T19:45:20Z"
}intake.completed
Sent when points have been credited.
{
"event": "intake.completed",
"source": "meal",
"transactionId": "meal-20251126-002",
"trackId": "5765279d-0e9b-4f16-b462-0f75d06d0c7a",
"userId": "08FE9Q",
"totalAmount": 6.20,
"currency": "GBP",
"ingredients": [
{
"foodId": "FOOD-221",
"name": "Quinoa",
"imageUrl": "https://example.com/images/foods/quinoa.jpg",
"weight": 150,
"unit": "g",
"quantity": 1,
"amount": 2.80,
"currency": "GBP",
"carbonImpactRating": 2,
"healthImpactRating": 1,
"co2e": 0.45,
"totalPoints": 40,
"totalCarbonPoints": 10,
"totalHealthPoints": 30
},
{
"foodId": "FOOD-874",
"name": "Avocado",
"imageUrl": "https://example.com/images/foods/avocado.jpg",
"weight": 100,
"unit": "g",
"quantity": 1,
"amount": 3.40,
"currency": "GBP",
"carbonImpactRating": 3,
"healthImpactRating": 2,
"co2e": 0.68,
"totalPoints": 22,
"totalHealthPoints": 16,
"totalCarbonPoints": 6
}
],
"totalPoints": 62,
"totalGreenProducts": 2,
"totalHealthyProducts": 2,
"totalCo2e": 1.13,
"totalHealthPoints": 40,
"totalCarbonPoints": 22,
"createdAt": "2025-11-26T19:45:25Z"
}intake.failed
Sent when processing fails.
{
"event": "intake.failed",
"source": "meal",
"transactionId": "meal-20251126-002",
"trackId": "5765279d-0e9b-4f16-b462-0f75d06d0c7a",
"userId": "08FE9Q",
"errorType": "DATA_INCOMPLETE",
"errorMessage": "Missing nutrition.energyKcal for ingredient FOOD-442",
"failedAt": "2025-11-26T19:47:10Z"
}Error types:
| Error Type | Description |
|---|---|
NO_VALID_FOOD_ITEMS | No valid food items found |
OLD_MEAL_LOG | Meal logged outside allowed time window |
DUPLICATE_MEAL | Duplicate transaction ID |
DATA_INCOMPLETE | Required data missing |
INTERNAL_ERROR | Server error |
Impact Ratings
Health and environmental impact ratings use a 1-5 scale:
| Rating | Grade | Meaning |
|---|---|---|
| 1 | A | Best |
| 2 | B | Good |
| 3 | C | Average |
| 4 | D | Below average |
| 5 | E | Worst |
Products rated A or B are counted as "green" (low environmental impact) or "healthy" (health) products.