API reference
Create an API key under Settings => User => API keys and send it as an x-api-key header or as Authorization: Bearer <key>. Replace https://norish.example.com in the examples with the address of your own server.
- Base URL
- https://norish.example.com/api/v1
- Authentication
x-api-key: <key>orAuthorization: Bearer <key>
Health
Check API health
GET/health
Reports API availability and verifies both the database connection and internal parser service are healthy.
Responses
The API, database, and parser service are healthy.
{
"status": "ok",
"db": {
"status": "ok"
},
"versions": {
"root": "string",
"apps": {},
"packages": {},
"scraper": "string"
},
"parser": {
"status": "ok",
"recipeScrapersVersion": "string"
}
}
Example request
curl "https://norish.example.com/api/v1/health" \
-H "x-api-key: $NORISH_API_KEY"
Recipes
Get a recipe by ID
GET/recipes/{id}
Parameters
idstring (uuid)pathrequired
Responses
Successful response
{
"id": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"userId": "string",
"name": "string",
"description": "string",
"image": "string",
"url": "string",
"servings": 1,
"prepMinutes": 1,
"cookMinutes": 1,
"totalMinutes": 1,
"notes": "string",
"systemUsed": "metric",
"originalSystem": "metric",
"calories": 1,
"fat": "string",
"carbs": "string",
"protein": "string",
"originCountry": "string",
"originCountryName": "string",
"originRegion": "string",
"provenanceNote": "string",
"dishColor": "string",
"createdAt": "string",
"updatedAt": "string",
"categories": [
"Breakfast"
],
"version": 1,
"recipeIngredients": [
{
"id": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"amount": 1,
"unit": "string",
"order": 1,
"systemUsed": "metric",
"version": 1,
"ingredientName": "string",
"ingredientId": "string"
}
],
"steps": [
{
"step": "string",
"systemUsed": "metric",
"order": 1,
"version": 1,
"images": [
{
"id": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"image": "string",
"order": 0,
"version": 1
}
],
"stepIngredients": [
{
"ingredientOrder": 1,
"share": 1,
"order": 1
}
]
}
],
"tags": [
{
"name": "string",
"version": 1
}
],
"cuisines": [
{
"id": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"name": "string",
"version": 1
}
],
"author": {
"id": "string",
"name": "string",
"image": "string",
"version": 1
},
"images": [
{
"id": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"image": "string",
"order": 0,
"generated": false,
"version": 1
}
],
"videos": [
{
"id": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"video": "string",
"thumbnail": "string",
"duration": 1,
"order": 0,
"version": 1
}
],
"nutritionEstimate": {
"lines": [
{
"key": "string",
"calories": 1,
"fat": 1,
"carbs": 1,
"protein": 1
}
]
}
}
Example request
curl "https://norish.example.com/api/v1/recipes/{id}" \
-H "x-api-key: $NORISH_API_KEY"
List recipes
POST/recipes/search
Returns a paginated list of recipes. All filter fields are optional, so you can omit them to fetch the default recipe list.
Request body
cursorintegerZero-based pagination offset. Defaults to 0.
Default 0 · Min 0
limitintegerMaximum number of recipes to return. Defaults to 50.
Default 50 · Min 1 · Max 200
searchstringOptional free-text search term.
searchFieldsstring[]Fields searched when
searchis provided. Defaults totitleandingredients.titledescriptioningredientsstepstagsDefault ["title","ingredients"]
tagsstring[]Optional tag filter.
categoriesstring[]Optional meal category filter.
BreakfastLunchDinnerSnackfilterModestringHow multi-value filters are combined. Defaults to
OR.ANDORDefault "OR"
sortModestringSort order for the returned recipes. Defaults to
dateDesc.titleAsctitleDescdateAscdateDescnoneDefault "dateDesc"
minRatingnumberOptional minimum recipe rating.
Min 1 · Max 5
maxCookingTimeintegerOptional maximum cooking time in minutes.
Min 1
Responses
Successful response
{
"recipes": [
{
"id": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"userId": "string",
"name": "string",
"description": "string",
"image": "string",
"url": "string",
"servings": 1,
"prepMinutes": 1,
"cookMinutes": 1,
"totalMinutes": 1,
"notes": "string",
"calories": 1,
"originCountry": "string",
"createdAt": "string",
"updatedAt": "string",
"categories": [
"Breakfast"
],
"version": 1,
"tags": [
{
"name": "string",
"version": 1
}
],
"author": {
"id": "string",
"name": "string",
"image": "string",
"version": 1
},
"averageRating": 1,
"ratingCount": 1
}
],
"total": 0,
"nextCursor": 0
}
Example request
curl -X POST "https://norish.example.com/api/v1/recipes/search" \
-H "x-api-key: $NORISH_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'
Create a recipe
POST/recipes
Creates a recipe directly from structured recipe data without parser transformation.
Request body
namestringrequireddescriptionstring | nullimagestring | nullurlstring | nullservingsintegerprepMinutesinteger | nullcookMinutesinteger | nulltotalMinutesinteger | nullnotesstring | nullsystemUsedstringmetricusoriginalSystemstring | nullmetricuscaloriesinteger | nullfatstring | nullcarbsstring | nullproteinstring | nulloriginCountrystring | nulloriginCountryNamestring | nulloriginRegionstring | nullprovenanceNotestring | nulldishColorstring | nullcategoriesstring[]BreakfastLunchDinnerSnackversionintegeridstring (uuid)recipeIngredientsobject[]tagsobject[]cuisinesstring (uuid)[]stepsobject[]imagesobject[]At most 10 items
videosobject[]
Responses
Successful response
"6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b"
Example request
curl -X POST "https://norish.example.com/api/v1/recipes" \
-H "x-api-key: $NORISH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "string"
}'
Recipe Imports
Queue a recipe import from a URL
POST/recipes/import/url
Request body
urlstringrequiredforceAIboolean
Responses
Successful response
{
"recipeId": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"status": "queued",
"recipe": {
"id": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"userId": "string",
"name": "string",
"description": "string",
"image": "string",
"url": "string",
"servings": 1,
"prepMinutes": 1,
"cookMinutes": 1,
"totalMinutes": 1,
"notes": "string",
"calories": 1,
"originCountry": "string",
"createdAt": "string",
"updatedAt": "string",
"categories": [
"Breakfast"
],
"version": 1,
"tags": [
{
"name": "string",
"version": 1
}
],
"author": {
"id": "string",
"name": "string",
"image": "string",
"version": 1
},
"averageRating": 1,
"ratingCount": 1
}
}
Example request
curl -X POST "https://norish.example.com/api/v1/recipes/import/url" \
-H "x-api-key: $NORISH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "string"
}'
Queue a recipe import from pasted text
POST/recipes/import/paste
Request body
textstringrequiredPasted recipe text. Supports plain text, JSON-LD recipe objects/arrays/@graph payloads, and YAML recipe mappings/arrays. Each recipe is limited to 100,000 characters. When forcing AI only one recipe will be parsed. JSON-LD single example: {"@type":"Recipe","name":"Toast","recipeIngredient":["2 slices bread"],"recipeInstructions":["Toast bread"]} JSON-LD multi example: [{"@type":"Recipe",...},{"@type":"Recipe",...}] YAML single example: title: Toast\ningredients:\n - 2 slices bread\nsteps:\n - Toast bread YAML multi example: - title: Toast\n ingredients:\n - 2 slices bread\n steps:\n - Toast bread
At least 1 characters
forceAIboolean
Responses
Successful response
{
"recipeIds": [
"6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b"
]
}
Example request
curl -X POST "https://norish.example.com/api/v1/recipes/import/paste" \
-H "x-api-key: $NORISH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"text": "string"
}'
Groceries
Get all groceries
GET/groceries
Responses
Successful response
[
{
"id": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"recipeIngredientId": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"name": "string",
"ingredientId": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"unit": "string",
"amount": 1,
"purchaseAmount": 1,
"isDone": true,
"sortOrder": 1,
"version": 1,
"recurringGroceryId": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"storeId": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b"
}
]
Example request
curl "https://norish.example.com/api/v1/groceries" \
-H "x-api-key: $NORISH_API_KEY"
Create a grocery
POST/groceries
Request body
namestring | nullrequiredunitstring | nullrequiredamountnumber | nullrequiredisDonebooleanDefault false
storeIdstring (uuid) | null
Responses
Successful response
{
"id": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"recipeIngredientId": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"name": "string",
"ingredientId": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"unit": "string",
"amount": 1,
"purchaseAmount": 1,
"isDone": true,
"sortOrder": 1,
"version": 1,
"recurringGroceryId": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"storeId": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b"
}
Example request
curl -X POST "https://norish.example.com/api/v1/groceries" \
-H "x-api-key: $NORISH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "string",
"unit": "string",
"amount": 1
}'
Mark a grocery as done
PATCH/groceries/{id}/done
Send the grocery's current version from the list. If it no longer matches, nothing changes and the response has stale: true: fetch the list again and retry.
Parameters
idstring (uuid)pathrequired
Request body
versionintegerrequiredAbove 0
Responses
Successful response
{
"grocery": {
"id": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"recipeIngredientId": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"name": "string",
"ingredientId": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"unit": "string",
"amount": 1,
"purchaseAmount": 1,
"isDone": true,
"sortOrder": 1,
"version": 1,
"recurringGroceryId": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"storeId": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b"
},
"stale": true
}
Example request
curl -X PATCH "https://norish.example.com/api/v1/groceries/{id}/done" \
-H "x-api-key: $NORISH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"version": 1
}'
Mark a grocery as not done
PATCH/groceries/{id}/undone
Send the grocery's current version from the list. If it no longer matches, nothing changes and the response has stale: true: fetch the list again and retry.
Parameters
idstring (uuid)pathrequired
Request body
versionintegerrequiredAbove 0
Responses
Successful response
{
"grocery": {
"id": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"recipeIngredientId": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"name": "string",
"ingredientId": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"unit": "string",
"amount": 1,
"purchaseAmount": 1,
"isDone": true,
"sortOrder": 1,
"version": 1,
"recurringGroceryId": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"storeId": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b"
},
"stale": true
}
Example request
curl -X PATCH "https://norish.example.com/api/v1/groceries/{id}/undone" \
-H "x-api-key: $NORISH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"version": 1
}'
Delete a grocery
DELETE/groceries/{id}
Send the grocery's current version from the list. If it no longer matches, nothing changes and the response has stale: true: fetch the list again and retry.
Parameters
idstring (uuid)pathrequiredversionintegerqueryrequiredAbove 0
Responses
Successful response
{
"success": true,
"stale": true
}
Example request
curl -X DELETE "https://norish.example.com/api/v1/groceries/{id}?version=1" \
-H "x-api-key: $NORISH_API_KEY"
Assign a grocery to a store
PATCH/groceries/{id}/store
Send the grocery's current version from the list; a stale version changes nothing and returns stale: true. A storeId of null removes the store. With savePreference (on by default) the same ingredient goes to this store next time.
Parameters
idstring (uuid)pathrequired
Request body
versionintegerrequiredAbove 0
storeIdstring (uuid) | nullrequiredsavePreferencebooleanDefault true
Responses
Successful response
{
"grocery": {
"id": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"recipeIngredientId": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"name": "string",
"ingredientId": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"unit": "string",
"amount": 1,
"purchaseAmount": 1,
"isDone": true,
"sortOrder": 1,
"version": 1,
"recurringGroceryId": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"storeId": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b"
},
"stale": true
}
Example request
curl -X PATCH "https://norish.example.com/api/v1/groceries/{id}/store" \
-H "x-api-key: $NORISH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"version": 1,
"storeId": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b"
}'
Stores
List stores
GET/stores
Responses
Successful response
[
{
"id": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"userId": "string",
"name": "string",
"color": "string",
"website": "string",
"searchAddress": "string",
"sortOrder": 1,
"version": 1,
"aisles": [
{
"id": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"storeId": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"name": "string",
"sortOrder": 1,
"version": 1
}
]
}
]
Example request
curl "https://norish.example.com/api/v1/stores" \
-H "x-api-key: $NORISH_API_KEY"
Create a store
POST/stores
Request body
idstring (uuid)namestringrequiredAt least 1 characters · At most 100 characters
colorstringprimarysecondarysuccesswarningdangerslateskyvioletDefault "primary"
websitestring (uri) | nullsearchAddressstring | null
Responses
Successful response
{
"id": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"userId": "string",
"name": "string",
"color": "string",
"website": "string",
"searchAddress": "string",
"sortOrder": 1,
"version": 1,
"aisles": [
{
"id": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"storeId": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"name": "string",
"sortOrder": 1,
"version": 1
}
]
}
Example request
curl -X POST "https://norish.example.com/api/v1/stores" \
-H "x-api-key: $NORISH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "string"
}'
Planned Recipes
Get planned recipes for today
GET/planned-recipes/today
"Today" is the day in the server's timezone.
Responses
Successful response
[
{
"id": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"date": "string",
"slot": "Breakfast",
"sortOrder": 1,
"recipeId": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"version": 1,
"recipeName": "string",
"recipeImage": "string",
"servings": 1,
"calories": 1
}
]
Example request
curl "https://norish.example.com/api/v1/planned-recipes/today" \
-H "x-api-key: $NORISH_API_KEY"
Get planned recipes for the current week
GET/planned-recipes/week
The week runs Monday through Sunday in the server's timezone.
Responses
Successful response
[
{
"id": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"date": "string",
"slot": "Breakfast",
"sortOrder": 1,
"recipeId": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"version": 1,
"recipeName": "string",
"recipeImage": "string",
"servings": 1,
"calories": 1
}
]
Example request
curl "https://norish.example.com/api/v1/planned-recipes/week" \
-H "x-api-key: $NORISH_API_KEY"
Get planned recipes for the current month
GET/planned-recipes/month
The month is the calendar month in the server's timezone.
Responses
Successful response
[
{
"id": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"date": "string",
"slot": "Breakfast",
"sortOrder": 1,
"recipeId": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"version": 1,
"recipeName": "string",
"recipeImage": "string",
"servings": 1,
"calories": 1
}
]
Example request
curl "https://norish.example.com/api/v1/planned-recipes/month" \
-H "x-api-key: $NORISH_API_KEY"
Create a planned recipe
POST/planned-recipes
Request body
idstring (uuid)datestringrequiredslotstringrequiredBreakfastLunchDinnerSnackrecipeIdstring (uuid)required
Responses
Successful response
{
"id": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b"
}
Example request
curl -X POST "https://norish.example.com/api/v1/planned-recipes" \
-H "x-api-key: $NORISH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"date": "string",
"slot": "Breakfast",
"recipeId": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b"
}'
Delete a planned recipe
DELETE/planned-recipes/{itemId}
Parameters
itemIdstring (uuid)pathrequiredversionintegerqueryrequiredAbove 0
Responses
Successful response
{
"success": true,
"stale": true
}
Example request
curl -X DELETE "https://norish.example.com/api/v1/planned-recipes/{itemId}?version=1" \
-H "x-api-key: $NORISH_API_KEY"