diff --git a/openapi/examples/AccountIn.yaml b/openapi/examples/AccountIn.yaml
new file mode 100644
index 0000000..87ac7f8
--- /dev/null
+++ b/openapi/examples/AccountIn.yaml
@@ -0,0 +1,6 @@
+summary: Account registration
+value:
+ {
+ "email": "ada@example.com",
+ "password": "FBR5h0L1ENA3edctgDIEnxSvFdR0",
+ }
\ No newline at end of file
diff --git a/openapi/examples/DipsOut.yaml b/openapi/examples/DipsOut.yaml
new file mode 100644
index 0000000..c21332b
--- /dev/null
+++ b/openapi/examples/DipsOut.yaml
@@ -0,0 +1,45 @@
+summary: Dip category search
+value:
+ [
+ {
+ "id": 3,
+ "name": "Muhammara",
+ "description": "Muhammara is a vibrant and flavorful dip or spread from the Middle East, particularly popular in Syrian and Turkish cuisine. It's made primarily from roasted red bell peppers, walnuts, breadcrumbs, and pomegranate molasses.",
+ "changedAt": "2025-08-08T16:24:44.673034Z",
+ "categories": [ "Dip" ],
+ "ingredients":
+ [
+ "Pomegranate molasses",
+ "Red bell peppers",
+ "Bread crumbs",
+ "Cumin",
+ "Chili flakes",
+ "Garlic",
+ "Walnuts",
+ "Lemon juice",
+ ],
+ "steps":
+ [
+ "Roast whole bell peppers in an oven or air fryer and wait until blackened on the outside.",
+ "Put the peppers in a bowl cover them with to let steam and cool down",
+ "Remove core seeds, and skins",
+ "Combine all the ingredients in a food processor.",
+ "Process until mostly smooth but with a little texture remaining.",
+ "Taste and adjust flavor as needed (lemon for acidity, pomegranate molasses for sweetness)",
+ ],
+ },
+ {
+ "id": 2,
+ "name": "Hummus",
+ "description": "Hummus, is a Levantine dip, spread, or savory dish made from cooked, mashed chickpeas blended with tahini, lemon juice, and garlic.",
+ "changedAt": "2025-08-08T16:24:44.579213Z",
+ "categories": [ "Dip" ],
+ "ingredients": [ "Chickpeas", "Tahini", "Lemon juice", "Garlic" ],
+ "steps":
+ [
+ "Combine chickpeas, tahini, lemon juice, garlic, and salt in a food processor.",
+ "Blend until smooth, slowly adding cold water until you reach a creamy consistency.",
+ "Taste and adjust seasoning if needed.",
+ ],
+ },
+ ]
diff --git a/openapi/examples/HummusV1In.yaml b/openapi/examples/HummusV1In.yaml
new file mode 100644
index 0000000..6b0b13a
--- /dev/null
+++ b/openapi/examples/HummusV1In.yaml
@@ -0,0 +1,15 @@
+summary: Hummus recipe
+value:
+ {
+ "name": "Hummus",
+ "description": "Hummus, is a Levantine dip, spread, or savory dish made from cooked, mashed chickpeas blended with tahini, lemon juice, and garlic.",
+ "categories": [ "Dip", "Spread" ],
+ "ingredients":
+ [ "Chickpeas", "Tahini", "Olive oil", "Lemon juice", "Garlic" ],
+ "steps":
+ [
+ "Combine chickpeas, tahini, olive oil, lemon juice, garlic, and salt in a food processor.",
+ "Blend until smooth, slowly adding cold water until you reach a creamy consistency.",
+ "Taste and adjust seasoning if needed.",
+ ],
+ }
diff --git a/openapi/examples/HummusV1Out.yaml b/openapi/examples/HummusV1Out.yaml
new file mode 100644
index 0000000..e94ef09
--- /dev/null
+++ b/openapi/examples/HummusV1Out.yaml
@@ -0,0 +1,17 @@
+summary: Hummus recipe
+value:
+ {
+ "id": 1729,
+ "name": "Hummus",
+ "description": "Hummus, is a Levantine dip, spread, or savory dish made from cooked, mashed chickpeas blended with tahini, lemon juice, and garlic.",
+ "changedAt": "2025-08-08T15:35:10.294411Z",
+ "categories": [ "Dip", "Spread" ],
+ "ingredients":
+ [ "Chickpeas", "Tahini", "Olive oil", "Lemon juice", "Garlic" ],
+ "steps":
+ [
+ "Combine chickpeas, tahini, olive oil, lemon juice, garlic, and salt in a food processor.",
+ "Blend until smooth, slowly adding cold water until you reach a creamy consistency.",
+ "Taste and adjust seasoning if needed.",
+ ],
+ }
diff --git a/openapi/examples/HummusV2In.yaml b/openapi/examples/HummusV2In.yaml
new file mode 100644
index 0000000..59ad8d8
--- /dev/null
+++ b/openapi/examples/HummusV2In.yaml
@@ -0,0 +1,14 @@
+summary: Hummus recipe update
+value:
+ {
+ "name": "Hummus",
+ "description": "Hummus, is a Levantine dip, spread, or savory dish made from cooked, mashed chickpeas blended with tahini, lemon juice, and garlic.",
+ "categories": [ "Dip", "Spread" ],
+ "ingredients": [ "Chickpeas", "Tahini", "Lemon juice", "Garlic" ],
+ "steps":
+ [
+ "Combine chickpeas, tahini, lemon juice, garlic, and salt in a food processor.",
+ "Blend until smooth, slowly adding cold water until you reach a creamy consistency.",
+ "Taste and adjust seasoning if needed.",
+ ],
+ }
diff --git a/openapi/examples/HummusV2Out.yaml b/openapi/examples/HummusV2Out.yaml
new file mode 100644
index 0000000..56959e0
--- /dev/null
+++ b/openapi/examples/HummusV2Out.yaml
@@ -0,0 +1,16 @@
+summary: Hummus recipe update
+value:
+ {
+ "id": 1729,
+ "name": "Hummus",
+ "description": "Hummus, is a Levantine dip, spread, or savory dish made from cooked, mashed chickpeas blended with tahini, lemon juice, and garlic.",
+ "changedAt": "2025-08-09T12:48:32.134821Z",
+ "categories": [ "Dip", "Spread" ],
+ "ingredients": [ "Chickpeas", "Tahini", "Lemon juice", "Garlic" ],
+ "steps":
+ [
+ "Combine chickpeas, tahini, lemon juice, garlic, and salt in a food processor.",
+ "Blend until smooth, slowly adding cold water until you reach a creamy consistency.",
+ "Taste and adjust seasoning if needed.",
+ ],
+ }
diff --git a/openapi/index.html b/openapi/index.html
new file mode 100644
index 0000000..530e213
--- /dev/null
+++ b/openapi/index.html
@@ -0,0 +1,24 @@
+
+
+
+ Recipe API Reference
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/openapi/openapi-2025-02-13.schema.json b/openapi/openapi-2025-02-13.schema.json
new file mode 100644
index 0000000..acaedec
--- /dev/null
+++ b/openapi/openapi-2025-02-13.schema.json
@@ -0,0 +1,1408 @@
+{
+ "$id": "https://spec.openapis.org/oas/3.1/schema/2025-02-13",
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "description": "The description of OpenAPI v3.1.x Documents without Schema Object validation",
+ "type": "object",
+ "properties": {
+ "openapi": {
+ "type": "string",
+ "pattern": "^3\\.1\\.\\d+(-.+)?$"
+ },
+ "info": {
+ "$ref": "#/$defs/info"
+ },
+ "jsonSchemaDialect": {
+ "type": "string",
+ "format": "uri-reference",
+ "default": "https://spec.openapis.org/oas/3.1/dialect/2024-11-10"
+ },
+ "servers": {
+ "type": "array",
+ "items": {
+ "$ref": "#/$defs/server"
+ },
+ "default": [
+ {
+ "url": "/"
+ }
+ ]
+ },
+ "paths": {
+ "$ref": "#/$defs/paths"
+ },
+ "webhooks": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/$defs/path-item"
+ }
+ },
+ "components": {
+ "$ref": "#/$defs/components"
+ },
+ "security": {
+ "type": "array",
+ "items": {
+ "$ref": "#/$defs/security-requirement"
+ }
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/$defs/tag"
+ }
+ },
+ "externalDocs": {
+ "$ref": "#/$defs/external-documentation"
+ }
+ },
+ "required": [
+ "openapi",
+ "info"
+ ],
+ "anyOf": [
+ {
+ "required": [
+ "paths"
+ ]
+ },
+ {
+ "required": [
+ "components"
+ ]
+ },
+ {
+ "required": [
+ "webhooks"
+ ]
+ }
+ ],
+ "$ref": "#/$defs/specification-extensions",
+ "unevaluatedProperties": false,
+ "$defs": {
+ "info": {
+ "$comment": "https://spec.openapis.org/oas/v3.1#info-object",
+ "type": "object",
+ "properties": {
+ "title": {
+ "type": "string"
+ },
+ "summary": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "termsOfService": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "contact": {
+ "$ref": "#/$defs/contact"
+ },
+ "license": {
+ "$ref": "#/$defs/license"
+ },
+ "version": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "title",
+ "version"
+ ],
+ "$ref": "#/$defs/specification-extensions",
+ "unevaluatedProperties": false
+ },
+ "contact": {
+ "$comment": "https://spec.openapis.org/oas/v3.1#contact-object",
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "url": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "email": {
+ "type": "string",
+ "format": "email"
+ }
+ },
+ "$ref": "#/$defs/specification-extensions",
+ "unevaluatedProperties": false
+ },
+ "license": {
+ "$comment": "https://spec.openapis.org/oas/v3.1#license-object",
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "identifier": {
+ "type": "string"
+ },
+ "url": {
+ "type": "string",
+ "format": "uri-reference"
+ }
+ },
+ "required": [
+ "name"
+ ],
+ "dependentSchemas": {
+ "identifier": {
+ "not": {
+ "required": [
+ "url"
+ ]
+ }
+ }
+ },
+ "$ref": "#/$defs/specification-extensions",
+ "unevaluatedProperties": false
+ },
+ "server": {
+ "$comment": "https://spec.openapis.org/oas/v3.1#server-object",
+ "type": "object",
+ "properties": {
+ "url": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "variables": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/$defs/server-variable"
+ }
+ }
+ },
+ "required": [
+ "url"
+ ],
+ "$ref": "#/$defs/specification-extensions",
+ "unevaluatedProperties": false
+ },
+ "server-variable": {
+ "$comment": "https://spec.openapis.org/oas/v3.1#server-variable-object",
+ "type": "object",
+ "properties": {
+ "enum": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "minItems": 1
+ },
+ "default": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "default"
+ ],
+ "$ref": "#/$defs/specification-extensions",
+ "unevaluatedProperties": false
+ },
+ "components": {
+ "$comment": "https://spec.openapis.org/oas/v3.1#components-object",
+ "type": "object",
+ "properties": {
+ "schemas": {
+ "type": "object",
+ "additionalProperties": {
+ "$dynamicRef": "#meta"
+ }
+ },
+ "responses": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/$defs/response-or-reference"
+ }
+ },
+ "parameters": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/$defs/parameter-or-reference"
+ }
+ },
+ "examples": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/$defs/example-or-reference"
+ }
+ },
+ "requestBodies": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/$defs/request-body-or-reference"
+ }
+ },
+ "headers": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/$defs/header-or-reference"
+ }
+ },
+ "securitySchemes": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/$defs/security-scheme-or-reference"
+ }
+ },
+ "links": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/$defs/link-or-reference"
+ }
+ },
+ "callbacks": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/$defs/callbacks-or-reference"
+ }
+ },
+ "pathItems": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/$defs/path-item"
+ }
+ }
+ },
+ "patternProperties": {
+ "^(schemas|responses|parameters|examples|requestBodies|headers|securitySchemes|links|callbacks|pathItems)$": {
+ "$comment": "Enumerating all of the property names in the regex above is necessary for unevaluatedProperties to work as expected",
+ "propertyNames": {
+ "pattern": "^[a-zA-Z0-9._-]+$"
+ }
+ }
+ },
+ "$ref": "#/$defs/specification-extensions",
+ "unevaluatedProperties": false
+ },
+ "paths": {
+ "$comment": "https://spec.openapis.org/oas/v3.1#paths-object",
+ "type": "object",
+ "patternProperties": {
+ "^/": {
+ "$ref": "#/$defs/path-item"
+ }
+ },
+ "$ref": "#/$defs/specification-extensions",
+ "unevaluatedProperties": false
+ },
+ "path-item": {
+ "$comment": "https://spec.openapis.org/oas/v3.1#path-item-object",
+ "type": "object",
+ "properties": {
+ "$ref": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "summary": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "servers": {
+ "type": "array",
+ "items": {
+ "$ref": "#/$defs/server"
+ }
+ },
+ "parameters": {
+ "type": "array",
+ "items": {
+ "$ref": "#/$defs/parameter-or-reference"
+ }
+ },
+ "get": {
+ "$ref": "#/$defs/operation"
+ },
+ "put": {
+ "$ref": "#/$defs/operation"
+ },
+ "post": {
+ "$ref": "#/$defs/operation"
+ },
+ "delete": {
+ "$ref": "#/$defs/operation"
+ },
+ "options": {
+ "$ref": "#/$defs/operation"
+ },
+ "head": {
+ "$ref": "#/$defs/operation"
+ },
+ "patch": {
+ "$ref": "#/$defs/operation"
+ },
+ "trace": {
+ "$ref": "#/$defs/operation"
+ }
+ },
+ "$ref": "#/$defs/specification-extensions",
+ "unevaluatedProperties": false
+ },
+ "operation": {
+ "$comment": "https://spec.openapis.org/oas/v3.1#operation-object",
+ "type": "object",
+ "properties": {
+ "tags": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "summary": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "externalDocs": {
+ "$ref": "#/$defs/external-documentation"
+ },
+ "operationId": {
+ "type": "string"
+ },
+ "parameters": {
+ "type": "array",
+ "items": {
+ "$ref": "#/$defs/parameter-or-reference"
+ }
+ },
+ "requestBody": {
+ "$ref": "#/$defs/request-body-or-reference"
+ },
+ "responses": {
+ "$ref": "#/$defs/responses"
+ },
+ "callbacks": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/$defs/callbacks-or-reference"
+ }
+ },
+ "deprecated": {
+ "default": false,
+ "type": "boolean"
+ },
+ "security": {
+ "type": "array",
+ "items": {
+ "$ref": "#/$defs/security-requirement"
+ }
+ },
+ "servers": {
+ "type": "array",
+ "items": {
+ "$ref": "#/$defs/server"
+ }
+ }
+ },
+ "$ref": "#/$defs/specification-extensions",
+ "unevaluatedProperties": false
+ },
+ "external-documentation": {
+ "$comment": "https://spec.openapis.org/oas/v3.1#external-documentation-object",
+ "type": "object",
+ "properties": {
+ "description": {
+ "type": "string"
+ },
+ "url": {
+ "type": "string",
+ "format": "uri-reference"
+ }
+ },
+ "required": [
+ "url"
+ ],
+ "$ref": "#/$defs/specification-extensions",
+ "unevaluatedProperties": false
+ },
+ "parameter": {
+ "$comment": "https://spec.openapis.org/oas/v3.1#parameter-object",
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "in": {
+ "enum": [
+ "query",
+ "header",
+ "path",
+ "cookie"
+ ]
+ },
+ "description": {
+ "type": "string"
+ },
+ "required": {
+ "default": false,
+ "type": "boolean"
+ },
+ "deprecated": {
+ "default": false,
+ "type": "boolean"
+ },
+ "schema": {
+ "$dynamicRef": "#meta"
+ },
+ "content": {
+ "$ref": "#/$defs/content",
+ "minProperties": 1,
+ "maxProperties": 1
+ }
+ },
+ "required": [
+ "name",
+ "in"
+ ],
+ "oneOf": [
+ {
+ "required": [
+ "schema"
+ ]
+ },
+ {
+ "required": [
+ "content"
+ ]
+ }
+ ],
+ "if": {
+ "properties": {
+ "in": {
+ "const": "query"
+ }
+ },
+ "required": [
+ "in"
+ ]
+ },
+ "then": {
+ "properties": {
+ "allowEmptyValue": {
+ "default": false,
+ "type": "boolean"
+ }
+ }
+ },
+ "dependentSchemas": {
+ "schema": {
+ "properties": {
+ "style": {
+ "type": "string"
+ },
+ "explode": {
+ "type": "boolean"
+ }
+ },
+ "allOf": [
+ {
+ "$ref": "#/$defs/examples"
+ },
+ {
+ "$ref": "#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-path"
+ },
+ {
+ "$ref": "#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-header"
+ },
+ {
+ "$ref": "#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-query"
+ },
+ {
+ "$ref": "#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-cookie"
+ },
+ {
+ "$ref": "#/$defs/styles-for-form"
+ }
+ ],
+ "$defs": {
+ "styles-for-path": {
+ "if": {
+ "properties": {
+ "in": {
+ "const": "path"
+ }
+ },
+ "required": [
+ "in"
+ ]
+ },
+ "then": {
+ "properties": {
+ "style": {
+ "default": "simple",
+ "enum": [
+ "matrix",
+ "label",
+ "simple"
+ ]
+ },
+ "required": {
+ "const": true
+ }
+ },
+ "required": [
+ "required"
+ ]
+ }
+ },
+ "styles-for-header": {
+ "if": {
+ "properties": {
+ "in": {
+ "const": "header"
+ }
+ },
+ "required": [
+ "in"
+ ]
+ },
+ "then": {
+ "properties": {
+ "style": {
+ "default": "simple",
+ "const": "simple"
+ }
+ }
+ }
+ },
+ "styles-for-query": {
+ "if": {
+ "properties": {
+ "in": {
+ "const": "query"
+ }
+ },
+ "required": [
+ "in"
+ ]
+ },
+ "then": {
+ "properties": {
+ "style": {
+ "default": "form",
+ "enum": [
+ "form",
+ "spaceDelimited",
+ "pipeDelimited",
+ "deepObject"
+ ]
+ },
+ "allowReserved": {
+ "default": false,
+ "type": "boolean"
+ }
+ }
+ }
+ },
+ "styles-for-cookie": {
+ "if": {
+ "properties": {
+ "in": {
+ "const": "cookie"
+ }
+ },
+ "required": [
+ "in"
+ ]
+ },
+ "then": {
+ "properties": {
+ "style": {
+ "default": "form",
+ "const": "form"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "$ref": "#/$defs/specification-extensions",
+ "unevaluatedProperties": false
+ },
+ "parameter-or-reference": {
+ "if": {
+ "type": "object",
+ "required": [
+ "$ref"
+ ]
+ },
+ "then": {
+ "$ref": "#/$defs/reference"
+ },
+ "else": {
+ "$ref": "#/$defs/parameter"
+ }
+ },
+ "request-body": {
+ "$comment": "https://spec.openapis.org/oas/v3.1#request-body-object",
+ "type": "object",
+ "properties": {
+ "description": {
+ "type": "string"
+ },
+ "content": {
+ "$ref": "#/$defs/content"
+ },
+ "required": {
+ "default": false,
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "content"
+ ],
+ "$ref": "#/$defs/specification-extensions",
+ "unevaluatedProperties": false
+ },
+ "request-body-or-reference": {
+ "if": {
+ "type": "object",
+ "required": [
+ "$ref"
+ ]
+ },
+ "then": {
+ "$ref": "#/$defs/reference"
+ },
+ "else": {
+ "$ref": "#/$defs/request-body"
+ }
+ },
+ "content": {
+ "$comment": "https://spec.openapis.org/oas/v3.1#fixed-fields-10",
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/$defs/media-type"
+ },
+ "propertyNames": {
+ "format": "media-range"
+ }
+ },
+ "media-type": {
+ "$comment": "https://spec.openapis.org/oas/v3.1#media-type-object",
+ "type": "object",
+ "properties": {
+ "schema": {
+ "$dynamicRef": "#meta"
+ },
+ "encoding": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/$defs/encoding"
+ }
+ }
+ },
+ "allOf": [
+ {
+ "$ref": "#/$defs/specification-extensions"
+ },
+ {
+ "$ref": "#/$defs/examples"
+ }
+ ],
+ "unevaluatedProperties": false
+ },
+ "encoding": {
+ "$comment": "https://spec.openapis.org/oas/v3.1#encoding-object",
+ "type": "object",
+ "properties": {
+ "contentType": {
+ "type": "string",
+ "format": "media-range"
+ },
+ "headers": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/$defs/header-or-reference"
+ }
+ },
+ "style": {
+ "default": "form",
+ "enum": [
+ "form",
+ "spaceDelimited",
+ "pipeDelimited",
+ "deepObject"
+ ]
+ },
+ "explode": {
+ "type": "boolean"
+ },
+ "allowReserved": {
+ "default": false,
+ "type": "boolean"
+ }
+ },
+ "allOf": [
+ {
+ "$ref": "#/$defs/specification-extensions"
+ },
+ {
+ "$ref": "#/$defs/styles-for-form"
+ }
+ ],
+ "unevaluatedProperties": false
+ },
+ "responses": {
+ "$comment": "https://spec.openapis.org/oas/v3.1#responses-object",
+ "type": "object",
+ "properties": {
+ "default": {
+ "$ref": "#/$defs/response-or-reference"
+ }
+ },
+ "patternProperties": {
+ "^[1-5](?:[0-9]{2}|XX)$": {
+ "$ref": "#/$defs/response-or-reference"
+ }
+ },
+ "minProperties": 1,
+ "$ref": "#/$defs/specification-extensions",
+ "unevaluatedProperties": false,
+ "if": {
+ "$comment": "either default, or at least one response code property must exist",
+ "patternProperties": {
+ "^[1-5](?:[0-9]{2}|XX)$": false
+ }
+ },
+ "then": {
+ "required": [
+ "default"
+ ]
+ }
+ },
+ "response": {
+ "$comment": "https://spec.openapis.org/oas/v3.1#response-object",
+ "type": "object",
+ "properties": {
+ "description": {
+ "type": "string"
+ },
+ "headers": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/$defs/header-or-reference"
+ }
+ },
+ "content": {
+ "$ref": "#/$defs/content"
+ },
+ "links": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/$defs/link-or-reference"
+ }
+ }
+ },
+ "required": [
+ "description"
+ ],
+ "$ref": "#/$defs/specification-extensions",
+ "unevaluatedProperties": false
+ },
+ "response-or-reference": {
+ "if": {
+ "type": "object",
+ "required": [
+ "$ref"
+ ]
+ },
+ "then": {
+ "$ref": "#/$defs/reference"
+ },
+ "else": {
+ "$ref": "#/$defs/response"
+ }
+ },
+ "callbacks": {
+ "$comment": "https://spec.openapis.org/oas/v3.1#callback-object",
+ "type": "object",
+ "$ref": "#/$defs/specification-extensions",
+ "additionalProperties": {
+ "$ref": "#/$defs/path-item"
+ }
+ },
+ "callbacks-or-reference": {
+ "if": {
+ "type": "object",
+ "required": [
+ "$ref"
+ ]
+ },
+ "then": {
+ "$ref": "#/$defs/reference"
+ },
+ "else": {
+ "$ref": "#/$defs/callbacks"
+ }
+ },
+ "example": {
+ "$comment": "https://spec.openapis.org/oas/v3.1#example-object",
+ "type": "object",
+ "properties": {
+ "summary": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "value": true,
+ "externalValue": {
+ "type": "string",
+ "format": "uri-reference"
+ }
+ },
+ "not": {
+ "required": [
+ "value",
+ "externalValue"
+ ]
+ },
+ "$ref": "#/$defs/specification-extensions",
+ "unevaluatedProperties": false
+ },
+ "example-or-reference": {
+ "if": {
+ "type": "object",
+ "required": [
+ "$ref"
+ ]
+ },
+ "then": {
+ "$ref": "#/$defs/reference"
+ },
+ "else": {
+ "$ref": "#/$defs/example"
+ }
+ },
+ "link": {
+ "$comment": "https://spec.openapis.org/oas/v3.1#link-object",
+ "type": "object",
+ "properties": {
+ "operationRef": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "operationId": {
+ "type": "string"
+ },
+ "parameters": {
+ "$ref": "#/$defs/map-of-strings"
+ },
+ "requestBody": true,
+ "description": {
+ "type": "string"
+ },
+ "body": {
+ "$ref": "#/$defs/server"
+ }
+ },
+ "oneOf": [
+ {
+ "required": [
+ "operationRef"
+ ]
+ },
+ {
+ "required": [
+ "operationId"
+ ]
+ }
+ ],
+ "$ref": "#/$defs/specification-extensions",
+ "unevaluatedProperties": false
+ },
+ "link-or-reference": {
+ "if": {
+ "type": "object",
+ "required": [
+ "$ref"
+ ]
+ },
+ "then": {
+ "$ref": "#/$defs/reference"
+ },
+ "else": {
+ "$ref": "#/$defs/link"
+ }
+ },
+ "header": {
+ "$comment": "https://spec.openapis.org/oas/v3.1#header-object",
+ "type": "object",
+ "properties": {
+ "description": {
+ "type": "string"
+ },
+ "required": {
+ "default": false,
+ "type": "boolean"
+ },
+ "deprecated": {
+ "default": false,
+ "type": "boolean"
+ },
+ "schema": {
+ "$dynamicRef": "#meta"
+ },
+ "content": {
+ "$ref": "#/$defs/content",
+ "minProperties": 1,
+ "maxProperties": 1
+ }
+ },
+ "oneOf": [
+ {
+ "required": [
+ "schema"
+ ]
+ },
+ {
+ "required": [
+ "content"
+ ]
+ }
+ ],
+ "dependentSchemas": {
+ "schema": {
+ "properties": {
+ "style": {
+ "default": "simple",
+ "const": "simple"
+ },
+ "explode": {
+ "default": false,
+ "type": "boolean"
+ }
+ },
+ "$ref": "#/$defs/examples"
+ }
+ },
+ "$ref": "#/$defs/specification-extensions",
+ "unevaluatedProperties": false
+ },
+ "header-or-reference": {
+ "if": {
+ "type": "object",
+ "required": [
+ "$ref"
+ ]
+ },
+ "then": {
+ "$ref": "#/$defs/reference"
+ },
+ "else": {
+ "$ref": "#/$defs/header"
+ }
+ },
+ "tag": {
+ "$comment": "https://spec.openapis.org/oas/v3.1#tag-object",
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "externalDocs": {
+ "$ref": "#/$defs/external-documentation"
+ }
+ },
+ "required": [
+ "name"
+ ],
+ "$ref": "#/$defs/specification-extensions",
+ "unevaluatedProperties": false
+ },
+ "reference": {
+ "$comment": "https://spec.openapis.org/oas/v3.1#reference-object",
+ "type": "object",
+ "properties": {
+ "$ref": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "summary": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ }
+ }
+ },
+ "schema": {
+ "$comment": "https://spec.openapis.org/oas/v3.1#schema-object",
+ "$dynamicAnchor": "meta",
+ "type": [
+ "object",
+ "boolean"
+ ]
+ },
+ "security-scheme": {
+ "$comment": "https://spec.openapis.org/oas/v3.1#security-scheme-object",
+ "type": "object",
+ "properties": {
+ "type": {
+ "enum": [
+ "apiKey",
+ "http",
+ "mutualTLS",
+ "oauth2",
+ "openIdConnect"
+ ]
+ },
+ "description": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "type"
+ ],
+ "allOf": [
+ {
+ "$ref": "#/$defs/specification-extensions"
+ },
+ {
+ "$ref": "#/$defs/security-scheme/$defs/type-apikey"
+ },
+ {
+ "$ref": "#/$defs/security-scheme/$defs/type-http"
+ },
+ {
+ "$ref": "#/$defs/security-scheme/$defs/type-http-bearer"
+ },
+ {
+ "$ref": "#/$defs/security-scheme/$defs/type-oauth2"
+ },
+ {
+ "$ref": "#/$defs/security-scheme/$defs/type-oidc"
+ }
+ ],
+ "unevaluatedProperties": false,
+ "$defs": {
+ "type-apikey": {
+ "if": {
+ "properties": {
+ "type": {
+ "const": "apiKey"
+ }
+ },
+ "required": [
+ "type"
+ ]
+ },
+ "then": {
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "in": {
+ "enum": [
+ "query",
+ "header",
+ "cookie"
+ ]
+ }
+ },
+ "required": [
+ "name",
+ "in"
+ ]
+ }
+ },
+ "type-http": {
+ "if": {
+ "properties": {
+ "type": {
+ "const": "http"
+ }
+ },
+ "required": [
+ "type"
+ ]
+ },
+ "then": {
+ "properties": {
+ "scheme": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "scheme"
+ ]
+ }
+ },
+ "type-http-bearer": {
+ "if": {
+ "properties": {
+ "type": {
+ "const": "http"
+ },
+ "scheme": {
+ "type": "string",
+ "pattern": "^[Bb][Ee][Aa][Rr][Ee][Rr]$"
+ }
+ },
+ "required": [
+ "type",
+ "scheme"
+ ]
+ },
+ "then": {
+ "properties": {
+ "bearerFormat": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "type-oauth2": {
+ "if": {
+ "properties": {
+ "type": {
+ "const": "oauth2"
+ }
+ },
+ "required": [
+ "type"
+ ]
+ },
+ "then": {
+ "properties": {
+ "flows": {
+ "$ref": "#/$defs/oauth-flows"
+ }
+ },
+ "required": [
+ "flows"
+ ]
+ }
+ },
+ "type-oidc": {
+ "if": {
+ "properties": {
+ "type": {
+ "const": "openIdConnect"
+ }
+ },
+ "required": [
+ "type"
+ ]
+ },
+ "then": {
+ "properties": {
+ "openIdConnectUrl": {
+ "type": "string",
+ "format": "uri-reference"
+ }
+ },
+ "required": [
+ "openIdConnectUrl"
+ ]
+ }
+ }
+ }
+ },
+ "security-scheme-or-reference": {
+ "if": {
+ "type": "object",
+ "required": [
+ "$ref"
+ ]
+ },
+ "then": {
+ "$ref": "#/$defs/reference"
+ },
+ "else": {
+ "$ref": "#/$defs/security-scheme"
+ }
+ },
+ "oauth-flows": {
+ "type": "object",
+ "properties": {
+ "implicit": {
+ "$ref": "#/$defs/oauth-flows/$defs/implicit"
+ },
+ "password": {
+ "$ref": "#/$defs/oauth-flows/$defs/password"
+ },
+ "clientCredentials": {
+ "$ref": "#/$defs/oauth-flows/$defs/client-credentials"
+ },
+ "authorizationCode": {
+ "$ref": "#/$defs/oauth-flows/$defs/authorization-code"
+ }
+ },
+ "$ref": "#/$defs/specification-extensions",
+ "unevaluatedProperties": false,
+ "$defs": {
+ "implicit": {
+ "type": "object",
+ "properties": {
+ "authorizationUrl": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "refreshUrl": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "scopes": {
+ "$ref": "#/$defs/map-of-strings"
+ }
+ },
+ "required": [
+ "authorizationUrl",
+ "scopes"
+ ],
+ "$ref": "#/$defs/specification-extensions",
+ "unevaluatedProperties": false
+ },
+ "password": {
+ "type": "object",
+ "properties": {
+ "tokenUrl": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "refreshUrl": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "scopes": {
+ "$ref": "#/$defs/map-of-strings"
+ }
+ },
+ "required": [
+ "tokenUrl",
+ "scopes"
+ ],
+ "$ref": "#/$defs/specification-extensions",
+ "unevaluatedProperties": false
+ },
+ "client-credentials": {
+ "type": "object",
+ "properties": {
+ "tokenUrl": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "refreshUrl": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "scopes": {
+ "$ref": "#/$defs/map-of-strings"
+ }
+ },
+ "required": [
+ "tokenUrl",
+ "scopes"
+ ],
+ "$ref": "#/$defs/specification-extensions",
+ "unevaluatedProperties": false
+ },
+ "authorization-code": {
+ "type": "object",
+ "properties": {
+ "authorizationUrl": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "tokenUrl": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "refreshUrl": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "scopes": {
+ "$ref": "#/$defs/map-of-strings"
+ }
+ },
+ "required": [
+ "authorizationUrl",
+ "tokenUrl",
+ "scopes"
+ ],
+ "$ref": "#/$defs/specification-extensions",
+ "unevaluatedProperties": false
+ }
+ }
+ },
+ "security-requirement": {
+ "$comment": "https://spec.openapis.org/oas/v3.1#security-requirement-object",
+ "type": "object",
+ "additionalProperties": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ "specification-extensions": {
+ "$comment": "https://spec.openapis.org/oas/v3.1#specification-extensions",
+ "patternProperties": {
+ "^x-": true
+ }
+ },
+ "examples": {
+ "properties": {
+ "example": true,
+ "examples": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/$defs/example-or-reference"
+ }
+ }
+ }
+ },
+ "map-of-strings": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ }
+ },
+ "styles-for-form": {
+ "if": {
+ "properties": {
+ "style": {
+ "const": "form"
+ }
+ },
+ "required": [
+ "style"
+ ]
+ },
+ "then": {
+ "properties": {
+ "explode": {
+ "default": true
+ }
+ }
+ },
+ "else": {
+ "properties": {
+ "explode": {
+ "default": false
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/openapi/parameters/{recipeId}.yaml b/openapi/parameters/{recipeId}.yaml
new file mode 100644
index 0000000..6d0a6a2
--- /dev/null
+++ b/openapi/parameters/{recipeId}.yaml
@@ -0,0 +1,6 @@
+name: "recipeId"
+in: "path"
+required: true
+schema:
+ $ref: "../schemas/Id.yaml"
+description: "Unique identifier of the recipe"
diff --git a/openapi/paths/account-register.yaml b/openapi/paths/account-register.yaml
new file mode 100644
index 0000000..be8cb03
--- /dev/null
+++ b/openapi/paths/account-register.yaml
@@ -0,0 +1,23 @@
+post:
+ tags:
+ - Account
+ summary: "Register an account"
+ operationId: "RegisterAccount"
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "../schemas/Account.yaml"
+ examples:
+ accountIn:
+ $ref: "../examples/AccountIn.yaml"
+ required: true
+ responses:
+ "200":
+ description: "OK"
+ "400":
+ description: "Bad Request"
+ content:
+ "application/json":
+ schema:
+ $ref: "../schemas/ProblemDetail.yaml"
diff --git a/openapi/paths/recipe-api-bundled.yaml b/openapi/paths/recipe-api-bundled.yaml
new file mode 100644
index 0000000..e69de29
diff --git a/openapi/paths/recipe-new.yaml b/openapi/paths/recipe-new.yaml
new file mode 100644
index 0000000..a0ef32b
--- /dev/null
+++ b/openapi/paths/recipe-new.yaml
@@ -0,0 +1,50 @@
+post:
+ tags:
+ - Recipe
+ summary: "Add a recipe"
+ operationId: "addRecipe"
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "../schemas/Recipe.yaml"
+ examples:
+ recipeIn:
+ $ref: "../examples/HummusV1In.yaml"
+ required: true
+ responses:
+ 200:
+ description: OK
+ content:
+ "application/json":
+ schema:
+ $ref: "../schemas/Recipe.yaml"
+ examples:
+ recipeOut:
+ $ref: "../examples/HummusV1Out.yaml"
+ 400:
+ description: Bad Request
+ content:
+ "application/json":
+ schema:
+ $ref: "../schemas/ProblemDetail.yaml"
+ 401:
+ description: Unauthorized
+ content:
+ "application/json":
+ schema:
+ $ref: "../schemas/ProblemDetail.yaml"
+ 403:
+ description: Forbidden
+ content:
+ "application/json":
+ schema:
+ $ref: "../schemas/ProblemDetail.yaml"
+ 404:
+ description: Not Found
+ content:
+ "application/json":
+ schema:
+ $ref: "../schemas/ProblemDetail.yaml"
+ security:
+ - httpBasic: [ ]
diff --git a/openapi/paths/recipe-search.yaml b/openapi/paths/recipe-search.yaml
new file mode 100644
index 0000000..ea36024
--- /dev/null
+++ b/openapi/paths/recipe-search.yaml
@@ -0,0 +1,59 @@
+get:
+ tags:
+ - Recipe
+ summary: "Find recipes"
+ operationId: "FindRecipes"
+ parameters:
+ - name: "category"
+ in: "query"
+ required: false
+ schema:
+ type: "string"
+ description: "Category name filter (equals, ignores case)"
+ - name: "recipe"
+ in: "query"
+ required: false
+ schema:
+ type: "string"
+ description: "Recipe name filter (contains, ignores case)"
+ - name: "limit"
+ in: "query"
+ required: true
+ schema:
+ type: "integer"
+ format: "int64"
+ minimum: 1
+ maximum: 100
+ description: "Maximum number of recipes to return"
+ - name: "offset"
+ in: "query"
+ required: true
+ schema:
+ type: "integer"
+ format: "int64"
+ minimum: 0
+ description: "Index into result set from where recipes will start to be returned"
+ responses:
+ 200:
+ description: OK
+ content:
+ "application/json":
+ schema:
+ type: "array"
+ items:
+ $ref: "../schemas/Recipe.yaml"
+ examples:
+ dipsOut:
+ $ref: "../examples/DipsOut.yaml"
+ 400:
+ description: Bad Request
+ content:
+ "application/json":
+ schema:
+ $ref: "../schemas/ProblemDetail.yaml"
+ 404:
+ description: Not Found
+ content:
+ "application/json":
+ schema:
+ $ref: "../schemas/ProblemDetail.yaml"
diff --git a/openapi/paths/recipe-{recipeId}.yaml b/openapi/paths/recipe-{recipeId}.yaml
new file mode 100644
index 0000000..b513833
--- /dev/null
+++ b/openapi/paths/recipe-{recipeId}.yaml
@@ -0,0 +1,117 @@
+get:
+ tags:
+ - Recipe
+ summary: "Get a recipe"
+ operationId: "GetRecipe"
+ parameters:
+ - $ref: "../parameters/{recipeId}.yaml"
+ responses:
+ 200:
+ description: OK
+ content:
+ "application/json":
+ schema:
+ $ref: "../schemas/Recipe.yaml"
+ examples:
+ recipeOut:
+ $ref: "../examples/HummusV1Out.yaml"
+ 400:
+ description: Bad Request
+ content:
+ "application/json":
+ schema:
+ $ref: "../schemas/ProblemDetail.yaml"
+ 404:
+ description: Not Found
+ content:
+ "application/json":
+ schema:
+ $ref: "../schemas/ProblemDetail.yaml"
+put:
+ tags:
+ - Recipe
+ summary: "Update a recipe"
+ operationId: "UpdateRecipe"
+ parameters:
+ - $ref: "../parameters/{recipeId}.yaml"
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "../schemas/Recipe.yaml"
+ examples:
+ recipeIn:
+ $ref: "../examples/HummusV2In.yaml"
+ required: true
+ responses:
+ 200:
+ description: OK
+ content:
+ "application/json":
+ schema:
+ $ref: "../schemas/Recipe.yaml"
+ examples:
+ recipeOut:
+ $ref: "../examples/HummusV2Out.yaml"
+ 400:
+ description: Bad Request
+ content:
+ "application/json":
+ schema:
+ $ref: "../schemas/ProblemDetail.yaml"
+ 401:
+ description: Unauthorized
+ content:
+ "application/json":
+ schema:
+ $ref: "../schemas/ProblemDetail.yaml"
+ 403:
+ description: Forbidden
+ content:
+ "application/json":
+ schema:
+ $ref: "../schemas/ProblemDetail.yaml"
+ 404:
+ description: Not Found
+ content:
+ "application/json":
+ schema:
+ $ref: "../schemas/ProblemDetail.yaml"
+ security:
+ - httpBasic: [ ]
+delete:
+ tags:
+ - Recipe
+ summary: "Remove a recipe"
+ operationId: "RemoveRecipe"
+ parameters:
+ - $ref: "../parameters/{recipeId}.yaml"
+ responses:
+ 200:
+ description: OK
+ 400:
+ description: Bad Request
+ content:
+ "application/json":
+ schema:
+ $ref: "../schemas/ProblemDetail.yaml"
+ 401:
+ description: Unauthorized
+ content:
+ "application/json":
+ schema:
+ $ref: "../schemas/ProblemDetail.yaml"
+ 403:
+ description: Forbidden
+ content:
+ "application/json":
+ schema:
+ $ref: "../schemas/ProblemDetail.yaml"
+ 404:
+ description: Not Found
+ content:
+ "application/json":
+ schema:
+ $ref: "../schemas/ProblemDetail.yaml"
+ security:
+ - httpBasic: [ ]
diff --git a/openapi/recipe-api-bundled.yaml b/openapi/recipe-api-bundled.yaml
new file mode 100644
index 0000000..504a0b6
--- /dev/null
+++ b/openapi/recipe-api-bundled.yaml
@@ -0,0 +1,497 @@
+openapi: 3.1.1
+info:
+ title: Recipe API
+ description: Programmatic access to a database of recipes. Users can publish and find recipes.
+ version: 1.0.0
+ license:
+ name: MIT
+ url: https://opensource.org/licenses/MIT
+servers:
+ - url: https://api.recipes.bitfield.eu/v1
+security: []
+tags:
+ - name: Recipe
+ description: Steps and ingredients for preparing a dish
+ - name: Account
+ description: Access to protected operations
+paths:
+ /recipe/new:
+ post:
+ tags:
+ - Recipe
+ summary: Add a recipe
+ operationId: addRecipe
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Recipe'
+ examples:
+ recipeIn:
+ $ref: '#/components/examples/HummusV1In'
+ required: true
+ responses:
+ '200':
+ description: OK
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Recipe'
+ examples:
+ recipeOut:
+ $ref: '#/components/examples/HummusV1Out'
+ '400':
+ description: Bad Request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetail'
+ '401':
+ description: Unauthorized
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetail'
+ '403':
+ description: Forbidden
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetail'
+ '404':
+ description: Not Found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetail'
+ security:
+ - httpBasic: []
+ /recipe/search:
+ get:
+ tags:
+ - Recipe
+ summary: Find recipes
+ operationId: FindRecipes
+ parameters:
+ - name: category
+ in: query
+ required: false
+ schema:
+ type: string
+ description: Category name filter (equals, ignores case)
+ - name: recipe
+ in: query
+ required: false
+ schema:
+ type: string
+ description: Recipe name filter (contains, ignores case)
+ - name: limit
+ in: query
+ required: true
+ schema:
+ type: integer
+ format: int64
+ minimum: 1
+ maximum: 100
+ description: Maximum number of recipes to return
+ - name: offset
+ in: query
+ required: true
+ schema:
+ type: integer
+ format: int64
+ minimum: 0
+ description: Index into result set from where recipes will start to be returned
+ responses:
+ '200':
+ description: OK
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/Recipe'
+ examples:
+ dipsOut:
+ $ref: '#/components/examples/DipsOut'
+ '400':
+ description: Bad Request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetail'
+ '404':
+ description: Not Found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetail'
+ /recipe/{recipeId}:
+ get:
+ tags:
+ - Recipe
+ summary: Get a recipe
+ operationId: GetRecipe
+ parameters:
+ - $ref: '#/components/parameters/{recipeId}'
+ responses:
+ '200':
+ description: OK
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Recipe'
+ examples:
+ recipeOut:
+ $ref: '#/components/examples/HummusV1Out'
+ '400':
+ description: Bad Request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetail'
+ '404':
+ description: Not Found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetail'
+ put:
+ tags:
+ - Recipe
+ summary: Update a recipe
+ operationId: UpdateRecipe
+ parameters:
+ - $ref: '#/components/parameters/{recipeId}'
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Recipe'
+ examples:
+ recipeIn:
+ $ref: '#/components/examples/HummusV2In'
+ required: true
+ responses:
+ '200':
+ description: OK
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Recipe'
+ examples:
+ recipeOut:
+ $ref: '#/components/examples/HummusV2Out'
+ '400':
+ description: Bad Request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetail'
+ '401':
+ description: Unauthorized
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetail'
+ '403':
+ description: Forbidden
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetail'
+ '404':
+ description: Not Found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetail'
+ security:
+ - httpBasic: []
+ delete:
+ tags:
+ - Recipe
+ summary: Remove a recipe
+ operationId: RemoveRecipe
+ parameters:
+ - $ref: '#/components/parameters/{recipeId}'
+ responses:
+ '200':
+ description: OK
+ '400':
+ description: Bad Request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetail'
+ '401':
+ description: Unauthorized
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetail'
+ '403':
+ description: Forbidden
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetail'
+ '404':
+ description: Not Found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetail'
+ security:
+ - httpBasic: []
+ /account/register:
+ post:
+ tags:
+ - Account
+ summary: Register an account
+ operationId: RegisterAccount
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Account'
+ examples:
+ accountIn:
+ $ref: '#/components/examples/AccountIn'
+ required: true
+ responses:
+ '200':
+ description: OK
+ '400':
+ description: Bad Request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetail'
+components:
+ securitySchemes:
+ httpBasic:
+ type: http
+ scheme: basic
+ schemas:
+ Id:
+ type: integer
+ format: int64
+ NonBlankString:
+ type: string
+ pattern: ^(.|\s)*\S(.|\s)*$
+ DateTime:
+ type: string
+ format: date-time
+ Recipe:
+ type: object
+ properties:
+ id:
+ $ref: '#/components/schemas/Id'
+ readOnly: true
+ description: Unique identifier of the recipe
+ name:
+ $ref: '#/components/schemas/NonBlankString'
+ description: Name
+ description:
+ $ref: '#/components/schemas/NonBlankString'
+ description: Description
+ changedAt:
+ $ref: '#/components/schemas/DateTime'
+ readOnly: true
+ description: Last point in time when the recipe was changed
+ categories:
+ type: array
+ items:
+ $ref: '#/components/schemas/NonBlankString'
+ description: Category names
+ ingredients:
+ type: array
+ minItems: 1
+ items:
+ $ref: '#/components/schemas/NonBlankString'
+ description: Step descriptions
+ steps:
+ type: array
+ minItems: 1
+ items:
+ $ref: '#/components/schemas/NonBlankString'
+ description: Ingredient names
+ required:
+ - name
+ - description
+ - categories
+ - ingredients
+ - steps
+ ProblemDetail:
+ type: object
+ properties:
+ type:
+ type: string
+ description: URI reference that identifies the problem type.
+ format: uri
+ status:
+ type: integer
+ description: HTTP status code generated by the origin server for this occurrence of the problem.
+ format: int32
+ title:
+ type: string
+ description: Short, human-readable summary of the problem type. It should not change from occurrence to occurrence of the problem, except for purposes of localization.
+ detail:
+ type: string
+ description: Human-readable explanation specific to this occurrence of the problem.
+ instance:
+ type: string
+ description: URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced.
+ properties:
+ type: object
+ description: Map for additional, non-standard properties
+ required:
+ - title
+ Email:
+ type: string
+ format: email
+ description: Email address
+ Password:
+ type: string
+ format: password
+ minLength: 8
+ description: Password
+ Account:
+ type: object
+ properties:
+ email:
+ $ref: '#/components/schemas/Email'
+ password:
+ $ref: '#/components/schemas/Password'
+ required:
+ - email
+ - password
+ examples:
+ HummusV1In:
+ summary: Hummus recipe
+ value:
+ name: Hummus
+ description: Hummus, is a Levantine dip, spread, or savory dish made from cooked, mashed chickpeas blended with tahini, lemon juice, and garlic.
+ categories:
+ - Dip
+ - Spread
+ ingredients:
+ - Chickpeas
+ - Tahini
+ - Olive oil
+ - Lemon juice
+ - Garlic
+ steps:
+ - Combine chickpeas, tahini, olive oil, lemon juice, garlic, and salt in a food processor.
+ - Blend until smooth, slowly adding cold water until you reach a creamy consistency.
+ - Taste and adjust seasoning if needed.
+ HummusV1Out:
+ summary: Hummus recipe
+ value:
+ id: 1729
+ name: Hummus
+ description: Hummus, is a Levantine dip, spread, or savory dish made from cooked, mashed chickpeas blended with tahini, lemon juice, and garlic.
+ changedAt: '2025-08-08T15:35:10.294411Z'
+ categories:
+ - Dip
+ - Spread
+ ingredients:
+ - Chickpeas
+ - Tahini
+ - Olive oil
+ - Lemon juice
+ - Garlic
+ steps:
+ - Combine chickpeas, tahini, olive oil, lemon juice, garlic, and salt in a food processor.
+ - Blend until smooth, slowly adding cold water until you reach a creamy consistency.
+ - Taste and adjust seasoning if needed.
+ DipsOut:
+ summary: Dip category search
+ value:
+ - id: 3
+ name: Muhammara
+ description: Muhammara is a vibrant and flavorful dip or spread from the Middle East, particularly popular in Syrian and Turkish cuisine. It's made primarily from roasted red bell peppers, walnuts, breadcrumbs, and pomegranate molasses.
+ changedAt: '2025-08-08T16:24:44.673034Z'
+ categories:
+ - Dip
+ ingredients:
+ - Pomegranate molasses
+ - Red bell peppers
+ - Bread crumbs
+ - Cumin
+ - Chili flakes
+ - Garlic
+ - Walnuts
+ - Lemon juice
+ steps:
+ - Roast whole bell peppers in an oven or air fryer and wait until blackened on the outside.
+ - Put the peppers in a bowl cover them with to let steam and cool down
+ - Remove core seeds, and skins
+ - Combine all the ingredients in a food processor.
+ - Process until mostly smooth but with a little texture remaining.
+ - Taste and adjust flavor as needed (lemon for acidity, pomegranate molasses for sweetness)
+ - id: 2
+ name: Hummus
+ description: Hummus, is a Levantine dip, spread, or savory dish made from cooked, mashed chickpeas blended with tahini, lemon juice, and garlic.
+ changedAt: '2025-08-08T16:24:44.579213Z'
+ categories:
+ - Dip
+ ingredients:
+ - Chickpeas
+ - Tahini
+ - Lemon juice
+ - Garlic
+ steps:
+ - Combine chickpeas, tahini, lemon juice, garlic, and salt in a food processor.
+ - Blend until smooth, slowly adding cold water until you reach a creamy consistency.
+ - Taste and adjust seasoning if needed.
+ HummusV2In:
+ summary: Hummus recipe update
+ value:
+ name: Hummus
+ description: Hummus, is a Levantine dip, spread, or savory dish made from cooked, mashed chickpeas blended with tahini, lemon juice, and garlic.
+ categories:
+ - Dip
+ - Spread
+ ingredients:
+ - Chickpeas
+ - Tahini
+ - Lemon juice
+ - Garlic
+ steps:
+ - Combine chickpeas, tahini, lemon juice, garlic, and salt in a food processor.
+ - Blend until smooth, slowly adding cold water until you reach a creamy consistency.
+ - Taste and adjust seasoning if needed.
+ HummusV2Out:
+ summary: Hummus recipe update
+ value:
+ id: 1729
+ name: Hummus
+ description: Hummus, is a Levantine dip, spread, or savory dish made from cooked, mashed chickpeas blended with tahini, lemon juice, and garlic.
+ changedAt: '2025-08-09T12:48:32.134821Z'
+ categories:
+ - Dip
+ - Spread
+ ingredients:
+ - Chickpeas
+ - Tahini
+ - Lemon juice
+ - Garlic
+ steps:
+ - Combine chickpeas, tahini, lemon juice, garlic, and salt in a food processor.
+ - Blend until smooth, slowly adding cold water until you reach a creamy consistency.
+ - Taste and adjust seasoning if needed.
+ AccountIn:
+ summary: Account registration
+ value:
+ email: ada@example.com
+ password: FBR5h0L1ENA3edctgDIEnxSvFdR0
+ parameters:
+ '{recipeId}':
+ name: recipeId
+ in: path
+ required: true
+ schema:
+ $ref: '#/components/schemas/Id'
+ description: Unique identifier of the recipe
diff --git a/openapi/recipe-api.yaml b/openapi/recipe-api.yaml
new file mode 100644
index 0000000..b89256c
--- /dev/null
+++ b/openapi/recipe-api.yaml
@@ -0,0 +1,32 @@
+openapi: "3.1.1"
+info:
+ title: "Recipe API"
+ description:
+ "Programmatic access to a database of recipes.
+ Users can publish and find recipes."
+ version: "1.0.0"
+ license:
+ name: MIT
+ url: https://opensource.org/licenses/MIT
+servers:
+ - url: "https://api.recipes.bitfield.eu/v1"
+tags:
+ - name: Recipe
+ description: "Steps and ingredients for preparing a dish"
+ - name: Account
+ description: "Access to protected operations"
+security: [ ]
+paths:
+ /recipe/new:
+ $ref: "./paths/recipe-new.yaml"
+ /recipe/search:
+ $ref: "./paths/recipe-search.yaml"
+ /recipe/{recipeId}:
+ $ref: "./paths/recipe-{recipeId}.yaml"
+ /account/register:
+ $ref: "./paths/account-register.yaml"
+components:
+ securitySchemes:
+ httpBasic:
+ type: http
+ scheme: basic
diff --git a/openapi/redocly.yaml b/openapi/redocly.yaml
new file mode 100644
index 0000000..6bc4331
--- /dev/null
+++ b/openapi/redocly.yaml
@@ -0,0 +1,8 @@
+extends:
+ - recommended
+
+apis:
+ recipe-api@v1:
+ root: ./recipe-api.yaml
+ rules:
+ no-ambiguous-paths: error
diff --git a/openapi/schemas/Account.yaml b/openapi/schemas/Account.yaml
new file mode 100644
index 0000000..84c8321
--- /dev/null
+++ b/openapi/schemas/Account.yaml
@@ -0,0 +1,9 @@
+type: "object"
+properties:
+ email:
+ $ref: "./Email.yaml"
+ password:
+ $ref: "./Password.yaml"
+required:
+ - email
+ - password
\ No newline at end of file
diff --git a/openapi/schemas/DateTime.yaml b/openapi/schemas/DateTime.yaml
new file mode 100644
index 0000000..71840df
--- /dev/null
+++ b/openapi/schemas/DateTime.yaml
@@ -0,0 +1,2 @@
+type: "string"
+format: date-time
\ No newline at end of file
diff --git a/openapi/schemas/Email.yaml b/openapi/schemas/Email.yaml
new file mode 100644
index 0000000..051183b
--- /dev/null
+++ b/openapi/schemas/Email.yaml
@@ -0,0 +1,3 @@
+type: "string"
+format: email
+description: "Email address"
\ No newline at end of file
diff --git a/openapi/schemas/Id.yaml b/openapi/schemas/Id.yaml
new file mode 100644
index 0000000..11a0416
--- /dev/null
+++ b/openapi/schemas/Id.yaml
@@ -0,0 +1,2 @@
+type: "integer"
+format: int64
\ No newline at end of file
diff --git a/openapi/schemas/NonBlankString.yaml b/openapi/schemas/NonBlankString.yaml
new file mode 100644
index 0000000..2cea67c
--- /dev/null
+++ b/openapi/schemas/NonBlankString.yaml
@@ -0,0 +1,2 @@
+type: "string"
+pattern: "^(.|\\s)*\\S(.|\\s)*$"
\ No newline at end of file
diff --git a/openapi/schemas/Password.yaml b/openapi/schemas/Password.yaml
new file mode 100644
index 0000000..7487470
--- /dev/null
+++ b/openapi/schemas/Password.yaml
@@ -0,0 +1,4 @@
+type: "string"
+format: password
+minLength: 8
+description: "Password"
\ No newline at end of file
diff --git a/openapi/schemas/ProblemDetail.yaml b/openapi/schemas/ProblemDetail.yaml
new file mode 100644
index 0000000..a9b5e55
--- /dev/null
+++ b/openapi/schemas/ProblemDetail.yaml
@@ -0,0 +1,24 @@
+type: object
+properties:
+ type:
+ type: "string"
+ description: "URI reference that identifies the problem type."
+ format: "uri"
+ status:
+ type: "integer"
+ description: "HTTP status code generated by the origin server for this occurrence of the problem."
+ format: "int32"
+ title:
+ type: "string"
+ description: "Short, human-readable summary of the problem type. It should not change from occurrence to occurrence of the problem, except for purposes of localization."
+ detail:
+ type: "string"
+ description: "Human-readable explanation specific to this occurrence of the problem."
+ instance:
+ type: "string"
+ description: "URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced."
+ properties:
+ type: "object"
+ description: "Map for additional, non-standard properties"
+required:
+ - title
diff --git a/openapi/schemas/Recipe.yaml b/openapi/schemas/Recipe.yaml
new file mode 100644
index 0000000..daf6080
--- /dev/null
+++ b/openapi/schemas/Recipe.yaml
@@ -0,0 +1,39 @@
+type: "object"
+properties:
+ id:
+ $ref: "./Id.yaml"
+ readOnly: true
+ description: "Unique identifier of the recipe"
+ name:
+ $ref: "./NonBlankString.yaml"
+ description: "Name"
+ description:
+ $ref: "./NonBlankString.yaml"
+ description: "Description"
+ changedAt:
+ $ref: "./DateTime.yaml"
+ readOnly: true
+ description: "Last point in time when the recipe was changed"
+ categories:
+ type: "array"
+ items:
+ $ref: "./NonBlankString.yaml"
+ description: "Category names"
+ ingredients:
+ type: "array"
+ minItems: 1
+ items:
+ $ref: "./NonBlankString.yaml"
+ description: "Step descriptions"
+ steps:
+ type: "array"
+ minItems: 1
+ items:
+ $ref: "./NonBlankString.yaml"
+ description: "Ingredient names"
+required:
+ - name
+ - description
+ - categories
+ - ingredients
+ - steps