{"openapi":"3.1.0","info":{"title":"Укошику (Ukoshiku) API","version":"0.1.0","description":"Публічний REST для читання стрічки рецептів без входу, плюс OAuth-ендпойнти MCP-сервера, яким агенти отримують доступ до особистих даних користувача, що явно це дозволив. Повний, з тулами й аргументами, опис агентського API — /.well-known/mcp/server-card.json. Кроки авторизації прозою — /auth.md."},"servers":[{"url":"https://dev.ukoshiku.com"}],"tags":[{"name":"feed","description":"Публічна стрічка рецептів. Без авторизації."},{"name":"mcp-oauth","description":"OAuth 2.1 для MCP-клієнтів (RFC 8414 / RFC 9728 / PKCE)."},{"name":"mcp","description":"MCP JSON-RPC 2.0 endpoint (Streamable HTTP)."},{"name":"search","description":"NLWeb /ask: пошук опублікованих рецептів природною мовою. Без авторизації."}],"paths":{"/api/social/feed":{"get":{"operationId":"listPublicFeed","tags":["feed"],"summary":"Наступна сторінка публічної стрічки рецептів","description":"Курсорна пагінація по опублікованих рецептах. Працює без сесії; з сесією додатково враховує власні лайки й підписки того, хто питає.","parameters":[{"name":"before","in":"query","required":true,"schema":{"type":"number"},"description":"Курсор: unix-час (мс) останнього вже отриманого елемента."},{"name":"sort","in":"query","schema":{"type":"string","enum":["fresh","popular","cheap"]},"description":"За замовчуванням fresh."},{"name":"q","in":"query","schema":{"type":"string"},"description":"Пошук за назвою/інгредієнтом."},{"name":"min","in":"query","schema":{"type":"number"},"description":"Мінімальна ціна порції."},{"name":"max","in":"query","schema":{"type":"number"},"description":"Максимальна ціна порції."},{"name":"time","in":"query","schema":{"type":"integer"},"description":"Максимальний час готування, хв."}],"responses":{"200":{"description":"До 10 публікацій.","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"author":{"type":"object","properties":{"id":{"type":"string"},"displayName":{"type":"string"}}},"servings":{"type":"integer"},"cookMinutes":{"type":["integer","null"]},"estimatedPrice":{"type":"integer","description":"У копійках/центах."}}}}}}}}},"400":{"description":"Немає або некоректний курсор.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Стрічку вимкнено.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"default":{"description":"Невідомий шлях під /api/: problem+json, не HTML.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/social/{id}":{"get":{"operationId":"getPublicRecipe","tags":["feed"],"summary":"Одна опублікована публікація зі складом","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Публікація з інгредієнтами та кроками."},"404":{"description":"Не знайдено.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"default":{"description":"Невідомий шлях під /api/: problem+json, не HTML.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/mcp/oauth/register":{"post":{"operationId":"registerMcpClient","tags":["mcp-oauth"],"summary":"Dynamic Client Registration (RFC 7591) для MCP-клієнта","description":"Відкрита реєстрація, без адмін-підтвердження: це реєстрація клієнта, не перевірка особи.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["redirect_uris"],"properties":{"client_name":{"type":"string"},"redirect_uris":{"type":"array","items":{"type":"string","format":"uri"},"minItems":1,"maxItems":10}}}}}},"responses":{"201":{"description":"Клієнта зареєстровано.","content":{"application/json":{"schema":{"type":"object","properties":{"client_id":{"type":"string"},"client_id_issued_at":{"type":"integer"},"client_name":{"type":"string"},"redirect_uris":{"type":"array","items":{"type":"string"}},"token_endpoint_auth_method":{"type":"string","enum":["none"]},"grant_types":{"type":"array","items":{"type":"string"}},"response_types":{"type":"array","items":{"type":"string"}}}}}}},"400":{"description":"invalid_client_metadata / invalid_redirect_uri","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/mcp/oauth/authorize":{"get":{"operationId":"authorizeMcpClient","tags":["mcp-oauth"],"summary":"Authorization Code + PKCE: людина логіниться і підтверджує доступ","description":"Браузерний крок, не для прямого виклику агентом: відкрий у людини, вона ввійде в свій акаунт Укошику та підтвердить клієнта. Відповідає 302 редиректом на redirect_uri з `code` (або `error`).","parameters":[{"name":"response_type","in":"query","required":true,"schema":{"type":"string","enum":["code"]}},{"name":"client_id","in":"query","required":true,"schema":{"type":"string"}},{"name":"redirect_uri","in":"query","required":true,"schema":{"type":"string","format":"uri"}},{"name":"code_challenge","in":"query","required":true,"schema":{"type":"string"}},{"name":"code_challenge_method","in":"query","required":true,"schema":{"type":"string","enum":["S256"]}},{"name":"scope","in":"query","required":true,"schema":{"type":"string"},"description":"Через пробіл: ukoshiku.read ukoshiku.drafts ukoshiku.plan ukoshiku.shopping-list"},{"name":"state","in":"query","schema":{"type":"string"}}],"responses":{"302":{"description":"Редирект на redirect_uri з code&state, або на /login."}}}},"/api/mcp/oauth/token":{"post":{"operationId":"exchangeMcpToken","tags":["mcp-oauth"],"summary":"Обмін authorization_code або refresh_token на access_token","requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"grant_type":{"type":"string","enum":["authorization_code","refresh_token"]},"client_id":{"type":"string"},"code":{"type":"string","description":"Тільки для authorization_code."},"redirect_uri":{"type":"string","description":"Тільки для authorization_code."},"code_verifier":{"type":"string","description":"PKCE, тільки для authorization_code."},"refresh_token":{"type":"string","description":"Тільки для refresh_token."}},"required":["grant_type","client_id"]}}}},"responses":{"200":{"description":"Токени видано.","content":{"application/json":{"schema":{"type":"object","properties":{"access_token":{"type":"string"},"refresh_token":{"type":"string"},"token_type":{"type":"string","enum":["Bearer"]},"expires_in":{"type":"integer","description":"Секунд, зараз 900 (15 хв)."},"scope":{"type":"string"}}}}}},"400":{"description":"invalid_request / unsupported_grant_type","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"invalid_client","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/mcp":{"post":{"operationId":"callMcp","tags":["mcp"],"summary":"MCP JSON-RPC 2.0 (Streamable HTTP)","description":"`initialize` не потребує токена. Усі інші методи (tools/list, tools/call, ping) — `Authorization: Bearer <access_token>` із /api/mcp/oauth/token. Повний список тулів і схем аргументів — /.well-known/mcp/server-card.json, покроково про авторизацію — /auth.md.","security":[{"mcpOAuth":["ukoshiku.read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["jsonrpc","method"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"]},"id":{"type":["string","integer","null"]},"method":{"type":"string","enum":["initialize","ping","tools/list","tools/call","notifications/initialized"]},"params":{"type":"object"}}}}}},"responses":{"200":{"description":"JSON-RPC result або error.","content":{"application/json":{"schema":{"type":"object","properties":{"jsonrpc":{"type":"string","enum":["2.0"]},"id":{"type":["string","integer","null"]},"result":{"type":"object"},"error":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"}}}}}}}},"400":{"description":"JSON-RPC parse error / invalid request.","content":{"application/json":{"schema":{"type":"object","properties":{"jsonrpc":{"type":"string","enum":["2.0"]},"id":{"type":["string","integer","null"]},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer"},"message":{"type":"string"}}}}}}}},"401":{"description":"Немає або недійсний Bearer-токен. WWW-Authenticate вказує на protected-resource metadata.","headers":{"WWW-Authenticate":{"schema":{"type":"string"},"description":"Bearer resource_metadata=\"https://dev.ukoshiku.com/.well-known/oauth-protected-resource/api/mcp\""}}}}}},"/api/docs-mcp":{"post":{"operationId":"callDocsMcp","tags":["mcp"],"summary":"MCP лише з документацією, без авторизації","description":"Тули ukoshiku_list_docs і ukoshiku_read_doc, а також resources/list і resources/read.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["jsonrpc","method"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"]},"id":{"type":["string","integer","null"]},"method":{"type":"string"},"params":{"type":"object"}}}}}},"responses":{"200":{"description":"JSON-RPC result або error.","content":{"application/json":{"schema":{"type":"object","properties":{"jsonrpc":{"type":"string","enum":["2.0"]},"id":{"type":["string","integer","null"]},"result":{"type":"object"},"error":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"}}}}}}}},"400":{"description":"JSON-RPC parse error / invalid request.","content":{"application/json":{"schema":{"type":"object","properties":{"jsonrpc":{"type":"string","enum":["2.0"]},"id":{"type":["string","integer","null"]},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer"},"message":{"type":"string"}}}}}}}}}}},"/ask":{"post":{"operationId":"askRecipes","tags":["search"],"summary":"NLWeb 0.55: пошук опублікованих рецептів","description":"Тіло `{\"query\":{\"text\":\"...\"},\"prefer\":{\"streaming\":false}}`. З `prefer.streaming: true` або `Accept: text/event-stream` відповідь іде SSE-подіями start, result, complete. Той самий пошук є й як GET /ask?query=...","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["query"],"properties":{"query":{"type":"object","required":["text"],"properties":{"text":{"type":"string"}}},"prefer":{"type":"object","properties":{"streaming":{"type":"boolean"}}}}}}}},"responses":{"200":{"description":"Відповідь NLWeb (answer) або SSE-потік.","content":{"application/json":{"schema":{"type":"object","required":["_meta","results"],"properties":{"_meta":{"type":"object","properties":{"response_type":{"type":"string","enum":["answer"]},"version":{"type":"string"}}},"results":{"type":"array","items":{"type":"object","description":"schema.org Recipe"}}}}},"text/event-stream":{"schema":{"type":"string"}}}},"400":{"description":"Тіло не JSON.","content":{"application/json":{"schema":{"type":"object","properties":{"_meta":{"type":"object","properties":{"response_type":{"type":"string","enum":["failure"]},"version":{"type":"string"}}},"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}}}}}}}}}}}},"components":{"schemas":{"ApiError":{"type":"object","description":"Помилка відомого ендпойнта: статус HTTP несе код, поле error — пояснення для людини.","properties":{"error":{"type":"string","description":"Людський опис помилки українською."}},"required":["error"]},"Problem":{"type":"object","description":"RFC 9457 Problem Details.","properties":{"type":{"type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"required":["type","title","status"]}},"securitySchemes":{"mcpOAuth":{"type":"oauth2","flows":{"authorizationCode":{"authorizationUrl":"https://dev.ukoshiku.com/api/mcp/oauth/authorize","tokenUrl":"https://dev.ukoshiku.com/api/mcp/oauth/token","scopes":{"ukoshiku.read":"Читати рецепти, план та історію","ukoshiku.drafts":"Пропонувати чернетку рецепта","ukoshiku.plan":"Пропонувати запис у план харчування","ukoshiku.shopping-list":"Пропонувати рядки списку покупок"}}}}}}}