Create a nutrition menu
POST/api/public/nutrition-menus
Create a nutrition menu. Provide traineeId (or escortId) to attach it to a coaching period, or omit both to leave it unassigned. Set isTemplate to save it as a reusable template. The daily macro totals (totalCalories/totalProtein/totalCarbs/totalFat) are required. Optionally add a meals breakdown: each meal has a title and a foodItems array, and each food carries per-100g macros so the app can scale by quantity.
Authentication
Requires an API key, sent as Authorization: Bearer cp_live_....
API keys carry scopes. Get the catalog of scopes and webhook events lists every scope.
Header parameters
| Name | Type | Description |
|---|---|---|
Idempotency-Key | string | Optional. Send a unique key (e.g. a UUID) to make this POST safe to retry. The same key within 24h returns the original result instead of creating a duplicate. |
Request body
application/json, required
| Name | Type | Description |
|---|---|---|
traineeId | string | Owning trainee id. Omit both traineeId and escortId to leave the menu unassigned. |
isTemplate | boolean | Save as a reusable template. A template cannot be assigned, so omit traineeId and escortId. |
escortId | string | Optional coaching period id |
namerequired | string | |
description | string | |
goal | string | |
type | string | |
totalCaloriesrequired | number | Calories/day. Required. |
totalProteinrequired | number | Grams of protein/day. Required. |
totalCarbsrequired | number | Grams of carbs/day. Required. |
totalFatrequired | number | Grams of fat/day. Required. |
meals | object[] | Daily meals (breakfast/lunch/dinner/snacks). |
meals[].id | string | Meal id. Not stable: saving the menu from the dashboard rewrites every meal with a new id, so re-read this endpoint before each write instead of caching it. Ignored on create; resend it on PATCH to keep the meal and its alternatives instead of replacing it with a new one. |
meals[].title | string | Meal name, e.g. "Breakfast". |
meals[].foodItems | object[] | |
meals[].foodItems[].name | string | Food name, e.g. "Chicken breast". |
meals[].foodItems[].quantity | number | Amount in the given unit, e.g. 150. |
meals[].foodItems[].unit | string | Unit of the quantity, e.g. "g", "ml", "unit". |
meals[].foodItems[].unitWeight | number | Grams in one unit. The app scales macros as quantity * unitWeight / 100 * the per-100g values, so send it whenever unit is not grams. Take it from the matching entry in the units array of |
meals[].foodItems[].productCode | number | Food database code, from |
meals[].foodItems[].caloriesPer100g | number | |
meals[].foodItems[].proteinPer100g | number | |
meals[].foodItems[].carbsPer100g | number | |
meals[].foodItems[].fatPer100g | number | |
meals[].calories | number | Total calories for the meal. |
meals[].protein | number | |
meals[].carbs | number | |
meals[].fat | number | |
meals[].notes | string | |
meals[].alternatives | object[] | Alternatives the trainee may eat instead of this meal. Manage them one at a time with POST/ |
meals[].alternatives[].id | string | Alternative id. Read-only; pass it to |
meals[].alternatives[].title | string | Alternative name, e.g. "Omelette instead". |
meals[].alternatives[].foodItems | object[] | |
meals[].alternatives[].foodItems[].name | string | Food name, e.g. "Chicken breast". |
meals[].alternatives[].foodItems[].quantity | number | Amount in the given unit, e.g. 150. |
meals[].alternatives[].foodItems[].unit | string | Unit of the quantity, e.g. "g", "ml", "unit". |
meals[].alternatives[].foodItems[].unitWeight | number | Grams in one unit. The app scales macros as quantity * unitWeight / 100 * the per-100g values, so send it whenever unit is not grams. Take it from the matching entry in the units array of |
meals[].alternatives[].foodItems[].productCode | number | Food database code, from |
meals[].alternatives[].foodItems[].caloriesPer100g | number | |
meals[].alternatives[].foodItems[].proteinPer100g | number | |
meals[].alternatives[].foodItems[].carbsPer100g | number | |
meals[].alternatives[].foodItems[].fatPer100g | number | |
meals[].alternatives[].calories | number | Total calories for the alternative. |
meals[].alternatives[].protein | number | |
meals[].alternatives[].carbs | number | |
meals[].alternatives[].fat | number | |
meals[].alternatives[].notes | string |
Request examples
curl --request POST \
--url 'https://api.coach-platform.com/api/public/nutrition-menus' \
--header 'Authorization: Bearer cp_live_...' \
--header 'Content-Type: application/json' \
--data '{
"name": "<string>",
"totalCalories": 123,
"totalProtein": 123,
"totalCarbs": 123,
"totalFat": 123
}'const response = await fetch('https://api.coach-platform.com/api/public/nutrition-menus', {
method: 'POST',
headers: {
Authorization: 'Bearer cp_live_...',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "<string>",
"totalCalories": 123,
"totalProtein": 123,
"totalCarbs": 123,
"totalFat": 123
}),
});
const data = await response.json();Responses
201 Created
{
"data": {
"id": "<string>",
"coach": "<string>",
"escorts": [
"<string>"
],
"title": "<string>",
"description": "<string>",
"goal": "<string>",
"type": "<string>",
"totalCalories": 123,
"totalProtein": 123,
"totalCarbs": 123,
"totalFat": 123,
"meals": [
{
"id": "<string>",
"title": "<string>",
"foodItems": [
{
"name": "<string>",
"quantity": 123,
"unit": "<string>",
"unitWeight": 123,
"productCode": 123,
"caloriesPer100g": 123,
"proteinPer100g": 123,
"carbsPer100g": 123,
"fatPer100g": 123
}
],
"calories": 123,
"protein": 123,
"carbs": 123,
"fat": 123,
"notes": "<string>",
"alternatives": [
{
"id": "<string>",
"title": "<string>",
"foodItems": [
{
"name": "<string>",
"quantity": 123,
"unit": "<string>",
"unitWeight": 123,
"productCode": 123,
"caloriesPer100g": 123,
"proteinPer100g": 123,
"carbsPer100g": 123,
"fatPer100g": 123
}
],
"calories": 123,
"protein": 123,
"carbs": 123,
"fat": 123,
"notes": "<string>"
}
]
}
],
"createdAt": "<date-time>"
},
"warnings": [
{
"field": "<string>",
"message": "<string>"
}
]
}Response fields (52)
| Name | Type | Description |
|---|---|---|
data | object | |
data.id | string | |
data.coach | string | |
data.escorts | string[] | |
data.title | string | |
data.description | string | |
data.goal | string | |
data.type | string | |
data.totalCalories | number | |
data.totalProtein | number | |
data.totalCarbs | number | |
data.totalFat | number | |
data.meals | object[] | |
data.meals[].id | string | Meal id. Not stable: saving the menu from the dashboard rewrites every meal with a new id, so re-read this endpoint before each write instead of caching it. Ignored on create; resend it on PATCH to keep the meal and its alternatives instead of replacing it with a new one. |
data.meals[].title | string | Meal name, e.g. "Breakfast". |
data.meals[].foodItems | object[] | |
data.meals[].foodItems[].name | string | Food name, e.g. "Chicken breast". |
data.meals[].foodItems[].quantity | number | Amount in the given unit, e.g. 150. |
data.meals[].foodItems[].unit | string | Unit of the quantity, e.g. "g", "ml", "unit". |
data.meals[].foodItems[].unitWeight | number | Grams in one unit. The app scales macros as quantity * unitWeight / 100 * the per-100g values, so send it whenever unit is not grams. Take it from the matching entry in the units array of |
data.meals[].foodItems[].productCode | number | Food database code, from |
data.meals[].foodItems[].caloriesPer100g | number | |
data.meals[].foodItems[].proteinPer100g | number | |
data.meals[].foodItems[].carbsPer100g | number | |
data.meals[].foodItems[].fatPer100g | number | |
data.meals[].calories | number | Total calories for the meal. |
data.meals[].protein | number | |
data.meals[].carbs | number | |
data.meals[].fat | number | |
data.meals[].notes | string | |
data.meals[].alternatives | object[] | Alternatives the trainee may eat instead of this meal. Manage them one at a time with POST/ |
data.meals[].alternatives[].id | string | Alternative id. Read-only; pass it to |
data.meals[].alternatives[].title | string | Alternative name, e.g. "Omelette instead". |
data.meals[].alternatives[].foodItems | object[] | |
data.meals[].alternatives[].foodItems[].name | string | Food name, e.g. "Chicken breast". |
data.meals[].alternatives[].foodItems[].quantity | number | Amount in the given unit, e.g. 150. |
data.meals[].alternatives[].foodItems[].unit | string | Unit of the quantity, e.g. "g", "ml", "unit". |
data.meals[].alternatives[].foodItems[].unitWeight | number | Grams in one unit. The app scales macros as quantity * unitWeight / 100 * the per-100g values, so send it whenever unit is not grams. Take it from the matching entry in the units array of |
data.meals[].alternatives[].foodItems[].productCode | number | Food database code, from |
data.meals[].alternatives[].foodItems[].caloriesPer100g | number | |
data.meals[].alternatives[].foodItems[].proteinPer100g | number | |
data.meals[].alternatives[].foodItems[].carbsPer100g | number | |
data.meals[].alternatives[].foodItems[].fatPer100g | number | |
data.meals[].alternatives[].calories | number | Total calories for the alternative. |
data.meals[].alternatives[].protein | number | |
data.meals[].alternatives[].carbs | number | |
data.meals[].alternatives[].fat | number | |
data.meals[].alternatives[].notes | string | |
data.createdAt | string<date-time> | |
warnings | object[] | Non-fatal problems with follow-up writes. The resource was created, but each listed field was not applied. |
warnings[].field | string | |
warnings[].message | string |
Error responses
400Bad Request401Unauthorized403Forbidden404Not Found409Conflict429Too Many Requests500Internal Server Error
These statuses share the same response body.
{
"error": {
"code": "<string>",
"message": "<string>",
"fix": "<string>",
"details": {},
"retryAfterMs": 123
}
}Error fields (6)
| Name | Type | Description |
|---|---|---|
error | object | |
error.code | string | |
error.message | string | |
error.fix | string | null | |
error.details | object | null | |
error.retryAfterMs | number | null |
More Nutrition Menus endpoints
This page is also available as Markdown. Browse it in the interactive explorer or download the OpenAPI spec.