Skip to main content

These are the docs for 0.26.0-beta, an older release.

See the current docs (0.26.1-beta) for what Norish does today.

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> or Authorization: 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.

Response
{
"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.

Response
{
"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.

Response
{
"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

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

  • cursorinteger

    Zero-based pagination offset. Defaults to 0.

    Default 0 · Min 0

  • limitinteger

    Maximum number of recipes to return. Defaults to 50.

    Default 50 · Min 1 · Max 200

  • searchstring

    Optional free-text search term.

  • searchFieldsstring[]

    Fields searched when search is provided. Defaults to title and ingredients.

    titledescriptioningredientsstepstags

    Default ["title","ingredients"]

  • tagsstring[]

    Optional tag filter.

  • categoriesstring[]

    Optional meal category filter.

    BreakfastLunchDinnerSnack
  • filterModestring

    How multi-value filters are combined. Defaults to OR.

    ANDOR

    Default "OR"

  • sortModestring

    Sort order for the returned recipes. Defaults to dateDesc.

    titleAsctitleDescdateAscdateDescnone

    Default "dateDesc"

  • minRatingnumber

    Optional minimum recipe rating.

    Min 1 · Max 5

  • maxCookingTimeinteger

    Optional maximum cooking time in minutes.

    Min 1

Responses

Successful response

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

  • namestringrequired
  • descriptionstring | null
  • urlstring | null
  • servingsinteger
  • prepMinutesinteger | null
  • cookMinutesinteger | null
  • totalMinutesinteger | null
  • notesstring | null
  • systemUsedstring
    metricus
  • originalSystemstring | null
    metricus
  • caloriesinteger | null
  • fatstring | null
  • carbsstring | null
  • proteinstring | null
  • originCountrystring | null
  • originCountryNamestring | null
  • originRegionstring | null
  • provenanceNotestring | null
  • dishColorstring | null
  • categoriesstring[]
    BreakfastLunchDinnerSnack
  • versioninteger
  • idstring (uuid)
  • recipeIngredientsobject[]
  • tagsobject[]
  • cuisinesstring (uuid)[]
  • stepsobject[]
  • imagesobject[]

    At most 10 items

  • videosobject[]

Responses

Successful response

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

  • urlstringrequired
  • forceAIboolean

Responses

Successful response

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)pathrequired

    The recipe ID assigned to the import job

  • waitintegerquery

    Min 0 · Max 60

Responses

Successful response

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

  • textstringrequired

    Pasted 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

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

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 | nullrequired
  • unitstring | nullrequired
  • amountnumber | nullrequired
  • isDoneboolean

    Default false

  • storeIdstring (uuid) | null

Responses

Successful response

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

  • versionintegerrequired

    Above 0

Responses

Successful response

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

  • versionintegerrequired

    Above 0

Responses

Successful response

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)pathrequired
  • versionintegerqueryrequired

    Above 0

Responses

Successful response

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

  • versionintegerrequired

    Above 0

  • storeIdstring (uuid) | nullrequired
  • savePreferenceboolean

    Default true

Responses

Successful response

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

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)
  • namestringrequired

    At least 1 characters · At most 100 characters

  • colorstring
    primarysecondarysuccesswarningdangerslateskyviolet

    Default "primary"

  • websitestring (uri) | null
  • searchAddressstring | null

Responses

Successful response

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

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

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

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)
  • datestringrequired
  • slotstringrequired
    BreakfastLunchDinnerSnack
  • recipeIdstring (uuid)required

Responses

Successful response

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)pathrequired
  • versionintegerqueryrequired

    Above 0

Responses

Successful response

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"