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.
Each key allows 500 requests an hour. Further requests return 429 Too Many Requests; the Retry-After header gives the wait in seconds.
- Base URL
- https://norish.example.com/api/v1
- Authentication
x-api-key: <key>orAuthorization: Bearer <key>
Health
Check whether Norish is alive
GET/health/live
Answers 200 while the server responds and its internal parser answers its own health check. Checks neither the database nor Valkey. Point restart probes (Docker HEALTHCHECK, Kubernetes liveness) here.
Responses
The server and its parser are alive.
{
"status": "ok",
"parser": {
"status": "string",
"statusCode": 1,
"recipeScrapersVersion": "string"
}
}
Example request
curl "https://norish.example.com/api/v1/health/live" \
-H "x-api-key: $NORISH_API_KEY"
Check whether Norish is ready for traffic
GET/health/ready
Answers 503 while the database or Valkey is unreachable, and 200 otherwise. The parser's state is in the response but does not fail the check. Point readiness probes and load balancers here.
Responses
The database and Valkey are healthy.
{
"status": "ok",
"db": {
"status": "ok"
},
"valkey": {
"status": "ok"
},
"parser": {
"status": "string",
"statusCode": 1,
"recipeScrapersVersion": "string"
}
}
Example request
curl "https://norish.example.com/api/v1/health/ready" \
-H "x-api-key: $NORISH_API_KEY"
Check whether Norish is ready for traffic (deprecated)
GET/health
Deprecated: use /health/live for restart probes and /health/ready for traffic. Answers exactly as /health/ready.
Responses
The database and Valkey are healthy.
{
"status": "ok",
"db": {
"status": "ok"
},
"valkey": {
"status": "ok"
},
"parser": {
"status": "string",
"statusCode": 1,
"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",
"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",
"joined": "together",
"amount": 1,
"unit": "string",
"order": 1,
"systemUsed": "metric",
"version": 1,
"ingredientName": "string",
"ingredientId": "string",
"foods": [
{
"ingredientId": "string",
"name": "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",
"url": "string",
"servings": 1,
"prepMinutes": 1,
"cookMinutes": 1,
"totalMinutes": 1,
"notes": "string",
"calories": 1,
"originCountry": "string",
"createdAt": "string",
"updatedAt": "string",
"categories": [
"Breakfast"
],
"version": 1,
"image": "string",
"tags": [
{
"name": "string",
"version": 1
}
],
"author": {
"id": "string",
"name": "string",
"image": "string",
"version": 1
},
"averageRating": 1,
"ratingCount": 1,
"pantryCoverage": 0
}
],
"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 | 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",
"url": "string",
"servings": 1,
"prepMinutes": 1,
"cookMinutes": 1,
"totalMinutes": 1,
"notes": "string",
"calories": 1,
"originCountry": "string",
"createdAt": "string",
"updatedAt": "string",
"categories": [
"Breakfast"
],
"version": 1,
"image": "string",
"tags": [
{
"name": "string",
"version": 1
}
],
"author": {
"id": "string",
"name": "string",
"image": "string",
"version": 1
},
"averageRating": 1,
"ratingCount": 1,
"pantryCoverage": 0
},
"statusUrl": "string"
}
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"
}'
Get status of a queued recipe import
GET/recipes/import/status/{recipeId}
Parameters
recipeIdstring (uuid)pathrequiredThe recipe ID assigned to the import job
waitintegerqueryMin 0 · Max 60
Responses
Successful response
{
"recipeId": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"url": "string",
"status": "waiting",
"step": "string",
"error": "string",
"recipe": {
"id": "6f1c2a7e-8b5d-4c1e-9f2a-6d7b8c9e0a1b",
"userId": "string",
"name": "string",
"description": "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,
"image": "string",
"tags": [
{
"name": "string",
"version": 1
}
],
"author": {
"id": "string",
"name": "string",
"image": "string",
"version": 1
},
"averageRating": 1,
"ratingCount": 1,
"pantryCoverage": 0
}
}
Example request
curl "https://norish.example.com/api/v1/recipes/import/status/{recipeId}" \
-H "x-api-key: $NORISH_API_KEY"
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
Answers with a grocery per food the name names: zout en peper is zout and peper. A name already on the list adds to that grocery.
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"