{ "openapi": "3.1.0", "info": { "title": "TMT API", "description": "또맛또(TMT) API", "version": "v1" }, "servers": [ { "url": "http://localhost:8080/api", "description": "Local" } ], "tags": [ { "name": "저장·리뷰 작성", "description": "명세 v2 — F. 리뷰 작성 · G. 이어쓰기 · I. 리뷰 상세" }, { "name": "매장 추천 (mock)", "description": "명세 v2 — J §5-2" }, { "name": "미디어", "description": "명세 v2 — F §3-2" }, { "name": "그룹 태그", "description": "명세 v2 — D_02 §2-1" }, { "name": "매장 검색", "description": "명세 v2 — B §2-2 · F §2-1" }, { "name": "리뷰 작성", "description": "명세 v2 — F. 리뷰 작성" }, { "name": "주소 검색 (mock)", "description": "명세 v2 — F §2-2" }, { "name": "큐레이션", "description": "명세 v2 — B. 근처 탐색 §2-4" }, { "name": "그룹", "description": "명세 v2 — D_01. 그룹 탐색 · D_02. 그룹 생성·상세·편집" }, { "name": "마이페이지·타인 프로필", "description": "명세 v2 — J · J-01" }, { "name": "리뷰 상세", "description": "명세 v2 — I §6-3·§6-4" }, { "name": "그룹 가입·리뷰 공유", "description": "명세 v2 — H. 그룹 게시(공유)" }, { "name": "근처 탐색", "description": "명세 v2 — B. 근처 탐색" }, { "name": "인증", "description": "명세 v2 — X. 로그인·회원가입" }, { "name": "홈", "description": "명세 v2 — A. 홈" }, { "name": "가게 상세", "description": "명세 v2 — B §3·§4" } ], "paths": { "/v1/saves/{saveId}": { "get": { "tags": [ "저장·리뷰 작성" ], "summary": "본인 상세", "description": "이어쓰기 재진입과 상세 시트를 모두 이걸로 그린다. 소유자에게만 응답한다 (S8).", "operationId": "getSave", "parameters": [ { "name": "saveId", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/SaveDetailResponse" } } } }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "401": { "description": "- `UNAUTHORIZED` — 로그인이 필요합니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "404": { "description": "- `SAVE_NOT_FOUND` — 저장을 찾을 수 없습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } }, "security": [ { "bearerAuth": [] } ] }, "put": { "tags": [ "저장·리뷰 작성" ], "summary": "작성 완료 (이어쓰기)", "description": "전체 교체다. 매장은 바꿀 수 없다 (S6). 서버는 같은 완성도 판정을 다시 돌린다 (C6).", "operationId": "updateSave", "parameters": [ { "name": "saveId", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "Idempotency-Key", "in": "header", "description": "재시도가 중복을 만들지 않게 하는 키. 앞뒤 공백은 제거되고, 최대 100자다 (규약 §9-1)", "required": true, "schema": { "type": "string", "example": "6f7d9c8e-4b2a-4f1e-9c3d-2a7b5e8f1c04", "maxLength": 100 } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SaveCreateRequest" } } }, "required": true }, "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/SaveResultResponse" } } } }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.\n- `REVIEW_TAG_NOT_FOUND` — 정의되지 않은 태그입니다.\n- `REVIEW_CONTENT_TOO_LONG` — 본문이 최대 길이를 초과했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "401": { "description": "- `UNAUTHORIZED` — 로그인이 필요합니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "403": { "description": "- `MEDIA_NOT_OWNED` — 본인이 발급받은 사진만 사용할 수 있습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "404": { "description": "- `SAVE_NOT_FOUND` — 저장을 찾을 수 없습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "409": { "description": "- `SAVE_ALREADY_REVIEWED` — 이미 리뷰가 완성된 저장은 수정할 수 없습니다.\n- `MEDIA_ALREADY_ATTACHED` — 이미 다른 저장에 사용된 사진입니다.\n- `IDEMPOTENCY_CONFLICT` — 같은 멱등성 키로 다른 요청이 이미 처리되었습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "422": { "description": "- `SAVE_PLACE_IMMUTABLE` — 저장의 매장은 변경할 수 없습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } }, "security": [ { "bearerAuth": [] } ] }, "delete": { "tags": [ "저장·리뷰 작성" ], "summary": "임시저장 버리기", "description": "`새로 작성하기`가 이전 임시저장을 버린다. 리뷰가 된 저장은 DELETE /v1/reviews/{reviewId} 소관이다 (F·G·I §5-2).", "operationId": "deleteSave", "parameters": [ { "name": "saveId", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "204": { "description": "No Content" }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "401": { "description": "- `UNAUTHORIZED` — 로그인이 필요합니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "404": { "description": "- `SAVE_NOT_FOUND` — 저장을 찾을 수 없습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "409": { "description": "- `SAVE_ALREADY_REVIEWED` — 이미 리뷰가 완성된 저장은 수정할 수 없습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } }, "security": [ { "bearerAuth": [] } ] } }, "/v1/places/{placeId}/favorite": { "put": { "tags": [ "가게 상세" ], "summary": "찜", "description": "멱등 토글 — 이미 찜한 매장에 다시 보내도 200이다 (F2).", "operationId": "addFavorite", "parameters": [ { "name": "placeId", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/FavoriteResponse" } } } }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "401": { "description": "- `UNAUTHORIZED` — 로그인이 필요합니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "404": { "description": "- `PLACE_NOT_FOUND` — 매장을 찾을 수 없습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } }, "security": [ { "bearerAuth": [] } ] }, "delete": { "tags": [ "가게 상세" ], "summary": "찜 해제", "description": "찜하지 않은 매장에 보내도 200이다 (F2).", "operationId": "removeFavorite", "parameters": [ { "name": "placeId", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/FavoriteResponse" } } } }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "401": { "description": "- `UNAUTHORIZED` — 로그인이 필요합니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "404": { "description": "- `PLACE_NOT_FOUND` — 매장을 찾을 수 없습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } }, "security": [ { "bearerAuth": [] } ] } }, "/v1/groups/{groupId}": { "get": { "tags": [ "그룹" ], "summary": "그룹 상세", "operationId": "groupDetail", "parameters": [ { "name": "groupId", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/GroupDetailResponse" } } } }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "404": { "description": "- `GROUP_NOT_FOUND` — 그룹을 찾을 수 없습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } }, "put": { "tags": [ "그룹" ], "summary": "그룹 편집", "description": "생성자만 호출할 수 있다 (G13). 전체 교체라 바꾸지 않는 필드도 현재 값을 실어 보내야 한다.", "operationId": "updateGroup", "parameters": [ { "name": "groupId", "in": "path", "required": true, "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GroupRequest" } } }, "required": true }, "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/GroupDetailResponse" } } } }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.\n- `GROUP_TAG_NOT_FOUND` — 정의되지 않은 카테고리·지역 값입니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "401": { "description": "- `UNAUTHORIZED` — 로그인이 필요합니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "403": { "description": "- `MEDIA_NOT_OWNED` — 본인이 발급받은 사진만 사용할 수 있습니다.\n- `GROUP_OWNER_REQUIRED` — 그룹 생성자만 할 수 있습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "404": { "description": "- `GROUP_NOT_FOUND` — 그룹을 찾을 수 없습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "409": { "description": "- `GROUP_NAME_DUPLICATED` — 같은 이름의 그룹이 있습니다.\n- `MEDIA_ALREADY_ATTACHED` — 이미 다른 저장에 사용된 사진입니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } }, "security": [ { "bearerAuth": [] } ] } }, "/v1/groups/{groupId}/review-shares": { "get": { "tags": [ "그룹 가입·리뷰 공유" ], "summary": "리뷰 공유 목록", "description": "내 리뷰 전체를 공유 여부와 함께 내린다 — PUT이 전체 교체라 현재 상태를 전부 알아야 한다.", "operationId": "listReviewShares", "parameters": [ { "name": "groupId", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "cursor", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32" } } ], "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/ReviewSharesResponse" } } } }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.\n- `INVALID_CURSOR` — 잘못된 커서입니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "401": { "description": "- `UNAUTHORIZED` — 로그인이 필요합니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "404": { "description": "- `GROUP_NOT_FOUND` — 그룹을 찾을 수 없습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } }, "security": [ { "bearerAuth": [] } ] }, "put": { "tags": [ "그룹 가입·리뷰 공유" ], "summary": "리뷰 공유 (전체 교체)", "description": "보낸 reviewIds가 이 그룹에 공유된 내 리뷰의 최종 집합이 된다. 멱등이라 Idempotency-Key가 필요 없다.", "operationId": "replaceReviewShares", "parameters": [ { "name": "groupId", "in": "path", "required": true, "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ReplaceSharesRequest" } } }, "required": true }, "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/ReplaceSharesResponse" } } } }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "401": { "description": "- `UNAUTHORIZED` — 로그인이 필요합니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "403": { "description": "- `GROUP_MEMBERSHIP_REQUIRED` — 가입하지 않은 그룹입니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "404": { "description": "- `GROUP_NOT_FOUND` — 그룹을 찾을 수 없습니다.\n- `REVIEW_NOT_FOUND` — 리뷰를 찾을 수 없습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } }, "security": [ { "bearerAuth": [] } ] } }, "/v1/saves": { "get": { "tags": [ "저장·리뷰 작성" ], "summary": "이어쓰기 목록", "description": "미완성 저장만 내려간다 (C5·R8). 정렬은 updatedAt DESC, saveId DESC.", "operationId": "listSaves", "parameters": [ { "name": "cursor", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32" } } ], "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/CursorPageSaveListItemResponse" } } } }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.\n- `INVALID_CURSOR` — 잘못된 커서입니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "401": { "description": "- `UNAUTHORIZED` — 로그인이 필요합니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } }, "security": [ { "bearerAuth": [] } ] }, "post": { "tags": [ "저장·리뷰 작성" ], "summary": "작성 완료 (신규)", "description": "완성도 판정(C4)을 충족하면 리뷰·티켓·매장 집계까지 같은 트랜잭션에서 나간다 (TX-1).", "operationId": "createSave", "parameters": [ { "name": "Idempotency-Key", "in": "header", "description": "재시도가 중복을 만들지 않게 하는 키. 앞뒤 공백은 제거되고, 최대 100자다 (규약 §9-1)", "required": true, "schema": { "type": "string", "example": "6f7d9c8e-4b2a-4f1e-9c3d-2a7b5e8f1c04", "maxLength": 100 } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SaveCreateRequest" } } }, "required": true }, "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/SaveResultResponse" } } } }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.\n- `PLACE_CATEGORY_NOT_FOUND` — 음식 카테고리가 목록에 없습니다.\n- `REVIEW_TAG_NOT_FOUND` — 정의되지 않은 태그입니다.\n- `REVIEW_CONTENT_TOO_LONG` — 본문이 최대 길이를 초과했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "401": { "description": "- `UNAUTHORIZED` — 로그인이 필요합니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "403": { "description": "- `MEDIA_NOT_OWNED` — 본인이 발급받은 사진만 사용할 수 있습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "404": { "description": "- `PLACE_NOT_FOUND` — 매장을 찾을 수 없습니다.\n- `ADDRESS_NOT_FOUND` — 주소를 찾을 수 없습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "409": { "description": "- `MEDIA_ALREADY_ATTACHED` — 이미 다른 저장에 사용된 사진입니다.\n- `IDEMPOTENCY_CONFLICT` — 같은 멱등성 키로 다른 요청이 이미 처리되었습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "502": { "description": "- `ADDRESS_PROVIDER_UNAVAILABLE` — 주소 서비스가 응답하지 않습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } }, "security": [ { "bearerAuth": [] } ] } }, "/v1/recommendations/places": { "post": { "tags": [ "매장 추천 (mock)" ], "summary": "매장 추천받기", "description": "내 리뷰를 근거로 아직 리뷰하지 않은 매장 1곳을 고른다 (A3).", "operationId": "recommendPlace", "parameters": [], "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/RecommendationResponse" } } } }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "401": { "description": "- `UNAUTHORIZED` — 로그인이 필요합니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "422": { "description": "- `RECOMMENDATION_UNAVAILABLE` — 추천의 근거가 될 리뷰가 없습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "503": { "description": "- `RECOMMENDATION_FAILED` — 매장 추천에 실패했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } }, "security": [ { "bearerAuth": [] } ] } }, "/v1/media/upload-intents": { "post": { "tags": [ "미디어" ], "summary": "사진 업로드 presigned URL 발급", "description": "발급받은 assetId는 발급받은 사용자만 Save에 붙일 수 있다 (M2).", "operationId": "createUploadIntent", "parameters": [], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UploadIntentRequest" } } }, "required": true }, "responses": { "201": { "description": "Created", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/UploadIntentResponse" } } } }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.\n- `MEDIA_CONTENT_TYPE_NOT_ALLOWED` — 허용되지 않는 파일 형식입니다.\n- `MEDIA_FILE_TOO_LARGE` — 파일이 허용 크기를 초과했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "401": { "description": "- `UNAUTHORIZED` — 로그인이 필요합니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } }, "security": [ { "bearerAuth": [] } ] } }, "/v1/groups": { "get": { "tags": [ "그룹" ], "summary": "그룹 탐색", "description": "검색·필터·정렬이 모두 한 목록에 걸린다. 파라미터 없이 부르면 추천순 전체 목록.", "operationId": "listGroups", "parameters": [ { "name": "query", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "foodCategoryId", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "regionTagIds", "in": "query", "required": false, "schema": { "type": "array", "items": { "type": "string" } } }, { "name": "sort", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "cursor", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32" } } ], "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/CursorPageGroupCardResponse" } } } }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.\n- `INVALID_CURSOR` — 잘못된 커서입니다.\n- `GROUP_TAG_NOT_FOUND` — 정의되지 않은 카테고리·지역 값입니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } }, "post": { "tags": [ "그룹" ], "summary": "그룹 만들기", "description": "4단계 입력을 한 번에 보낸다. 요청자가 자동으로 그룹장이 되고 소유권은 이전되지 않는다 (G13).", "operationId": "createGroup", "parameters": [], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GroupRequest" } } }, "required": true }, "responses": { "201": { "description": "Created", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/GroupDetailResponse" } } } }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.\n- `GROUP_TAG_NOT_FOUND` — 정의되지 않은 카테고리·지역 값입니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "401": { "description": "- `UNAUTHORIZED` — 로그인이 필요합니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "403": { "description": "- `MEDIA_NOT_OWNED` — 본인이 발급받은 사진만 사용할 수 있습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "409": { "description": "- `GROUP_NAME_DUPLICATED` — 같은 이름의 그룹이 있습니다.\n- `MEDIA_ALREADY_ATTACHED` — 이미 다른 저장에 사용된 사진입니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } }, "security": [ { "bearerAuth": [] } ] } }, "/v1/groups/{groupId}/memberships": { "post": { "tags": [ "그룹 가입·리뷰 공유" ], "summary": "가입", "description": "티켓 1장을 소비해 가입한다. 티켓 소비·멤버십 생성·자동 공유가 한 트랜잭션이다 (TX-3).", "operationId": "join", "parameters": [ { "name": "groupId", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "Idempotency-Key", "in": "header", "description": "재시도가 중복을 만들지 않게 하는 키. 앞뒤 공백은 제거되고, 최대 100자다 (규약 §9-1)", "required": true, "schema": { "type": "string", "example": "6f7d9c8e-4b2a-4f1e-9c3d-2a7b5e8f1c04", "maxLength": 100 } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JoinRequest" } } } }, "responses": { "201": { "description": "Created", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/JoinResponse" } } } }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "401": { "description": "- `UNAUTHORIZED` — 로그인이 필요합니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "404": { "description": "- `GROUP_NOT_FOUND` — 그룹을 찾을 수 없습니다.\n- `REVIEW_NOT_FOUND` — 리뷰를 찾을 수 없습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "409": { "description": "- `ALREADY_GROUP_MEMBER` — 이미 가입한 그룹입니다.\n- `GROUP_JOIN_TICKET_REQUIRED` — 그룹 가입에 필요한 티켓이 부족합니다.\n- `IDEMPOTENCY_CONFLICT` — 같은 멱등성 키로 다른 요청이 이미 처리되었습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } }, "security": [ { "bearerAuth": [] } ] } }, "/v1/auth/token/refresh": { "post": { "tags": [ "인증" ], "summary": "토큰 재발급", "description": "refresh 토큰으로 access·refresh 토큰을 새로 발급한다.\n\nrefresh까지 만료(AUTH_TOKEN_EXPIRED)거나 유효하지 않으면(AUTH_TOKEN_INVALID) 재로그인으로 분기한다.", "operationId": "refreshToken", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TokenRefreshRequest" } } }, "required": true }, "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/TokenRefreshResponse" } } } }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "401": { "description": "- `AUTH_TOKEN_INVALID` — 유효하지 않은 토큰입니다.\n- `AUTH_TOKEN_EXPIRED` — 만료된 토큰입니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } } }, "/v1/auth/login/kakao": { "post": { "tags": [ "인증" ], "summary": "카카오 로그인", "description": "카카오 인가 코드를 교환해 로그인하고 토큰을 발급한다. 처음 온 카카오 계정이면 users 행을 만들고 `isNewUser=true`로 내린다 — FE는 이 값으로 온보딩(TMT-273)에 분기한다.\n\n`redirectUri`는 인가 요청에 사용한 값과 같아야 한다. 이후 요청은 `Authorization: Bearer {accessToken}`으로 보내고, 만료(AUTH_TOKEN_EXPIRED)되면 재발급 API로 갱신한다.", "operationId": "loginWithKakao", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/KakaoLoginRequest" } } }, "required": true }, "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/KakaoLoginResponse" } } } }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "401": { "description": "- `AUTH_KAKAO_CODE_INVALID` — 카카오 인가 코드가 유효하지 않습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "502": { "description": "- `AUTH_KAKAO_UNAVAILABLE` — 카카오 로그인 서비스가 응답하지 않습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } } }, "/health/error-test-tmt": { "post": { "tags": [ "health-check-controller" ], "operationId": "errorTestTmt", "responses": { "200": { "description": "OK" }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.\n- `INTERNAL_ERROR_TEST` — 에러 테스트용 예외입니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } } }, "/health/error-test-global": { "post": { "tags": [ "health-check-controller" ], "operationId": "errorTestGlobal", "responses": { "200": { "description": "OK" }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } } }, "/v1/users/{userId}": { "get": { "tags": [ "마이페이지·타인 프로필" ], "summary": "타인 프로필 상단", "description": "email·availableTicketCount는 본인에게만 내린다 (U7). 인증은 선택이다.", "operationId": "userProfile", "parameters": [ { "name": "userId", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/UserProfileResponse" } } } }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "404": { "description": "- `USER_NOT_FOUND` — 사용자를 찾을 수 없습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } } }, "/v1/users/{userId}/reviews": { "get": { "tags": [ "마이페이지·타인 프로필" ], "summary": "타인 리뷰 탭", "description": "saveId는 소유자만 쓰는 핸들이라 빠진다 (S8). 개인 프로필에는 게이트가 없다 (G2).", "operationId": "userReviews", "parameters": [ { "name": "userId", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "cursor", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32" } } ], "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/CursorPageUserReviewGridItem" } } } }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.\n- `INVALID_CURSOR` — 잘못된 커서입니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "404": { "description": "- `USER_NOT_FOUND` — 사용자를 찾을 수 없습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } } }, "/v1/users/{userId}/groups": { "get": { "tags": [ "마이페이지·타인 프로필" ], "summary": "타인 그룹 탭", "description": "matchedSavedPlaceCount는 조회자 기준이라 비로그인이면 0이다 (§6-1).", "operationId": "userGroups", "parameters": [ { "name": "userId", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "cursor", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32" } } ], "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/CursorPageGroupCardResponse" } } } }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.\n- `INVALID_CURSOR` — 잘못된 커서입니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "404": { "description": "- `USER_NOT_FOUND` — 사용자를 찾을 수 없습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } } }, "/v1/users/{userId}/favorites": { "get": { "tags": [ "마이페이지·타인 프로필" ], "summary": "타인 좋아요 탭", "description": "isFavorite은 조회자 기준이라 여기서는 true가 아닐 수 있다 (F3).", "operationId": "userFavorites", "parameters": [ { "name": "userId", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "latitude", "in": "query", "required": false, "schema": { "type": "number", "format": "double" } }, { "name": "longitude", "in": "query", "required": false, "schema": { "type": "number", "format": "double" } }, { "name": "cursor", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32" } } ], "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/CursorPagePlaceCardResponse" } } } }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.\n- `INVALID_CURSOR` — 잘못된 커서입니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "404": { "description": "- `USER_NOT_FOUND` — 사용자를 찾을 수 없습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } } }, "/v1/users/me": { "get": { "tags": [ "마이페이지·타인 프로필" ], "summary": "마이페이지 상단", "description": "프로필·티켓 배너·칩 카운트 3종. 칩 숫자는 탭을 열기 전에 보이므로 여기 함께 싣는다 (J §2).", "operationId": "me", "parameters": [], "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/MyProfileResponse" } } } }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "401": { "description": "- `UNAUTHORIZED` — 로그인이 필요합니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } }, "security": [ { "bearerAuth": [] } ] } }, "/v1/users/me/tickets": { "get": { "tags": [ "마이페이지·타인 프로필" ], "summary": "내 티켓", "description": "발급·소비·회수 이력과 잔액. 미완성 저장은 목록에 섞이지 않고 inProgressSaveCount 하나로 내린다 — 0보다 크면 화면이 상단 `작성 중` 배너를 그린다 (T10, J §4-1).", "operationId": "myTickets", "parameters": [ { "name": "cursor", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32" } } ], "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/TicketHistoryResponse" } } } }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.\n- `INVALID_CURSOR` — 잘못된 커서입니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "401": { "description": "- `UNAUTHORIZED` — 로그인이 필요합니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } }, "security": [ { "bearerAuth": [] } ] } }, "/v1/users/me/reviews": { "get": { "tags": [ "마이페이지·타인 프로필" ], "summary": "내 리뷰 탭", "description": "2열 사진 그리드라 카드가 아니라 썸네일만 내린다. 사진 0장 리뷰(C4-1)는 thumbnailUrl이 null이다. 미완성 저장은 나오지 않는다 (R8).", "operationId": "myReviews", "parameters": [ { "name": "cursor", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32" } } ], "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/CursorPageMyReviewGridItem" } } } }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.\n- `INVALID_CURSOR` — 잘못된 커서입니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "401": { "description": "- `UNAUTHORIZED` — 로그인이 필요합니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } }, "security": [ { "bearerAuth": [] } ] } }, "/v1/users/me/groups": { "get": { "tags": [ "마이페이지·타인 프로필" ], "summary": "내 그룹 탭", "description": "가입 오래된 순 — 홈의 myGroups와 같은 기준이라 두 화면에서 순서가 어긋나지 않는다 (G20).", "operationId": "myGroups", "parameters": [ { "name": "cursor", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32" } } ], "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/CursorPageGroupCardResponse" } } } }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.\n- `INVALID_CURSOR` — 잘못된 커서입니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "401": { "description": "- `UNAUTHORIZED` — 로그인이 필요합니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } }, "security": [ { "bearerAuth": [] } ] } }, "/v1/users/me/favorites": { "get": { "tags": [ "마이페이지·타인 프로필" ], "summary": "내 좋아요 탭", "description": "찜한 매장 목록 (F1). 이 목록에서 isFavorite은 항상 true다.", "operationId": "myFavorites", "parameters": [ { "name": "latitude", "in": "query", "required": false, "schema": { "type": "number", "format": "double" } }, { "name": "longitude", "in": "query", "required": false, "schema": { "type": "number", "format": "double" } }, { "name": "cursor", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32" } } ], "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/CursorPagePlaceCardResponse" } } } }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.\n- `INVALID_CURSOR` — 잘못된 커서입니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "401": { "description": "- `UNAUTHORIZED` — 로그인이 필요합니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } }, "security": [ { "bearerAuth": [] } ] } }, "/v1/reviews/{reviewId}": { "get": { "tags": [ "리뷰 상세" ], "summary": "공개 리뷰 상세", "description": "완성된 리뷰만 대상이고 미완성 저장은 조회되지 않는다 (R8). 개인 리뷰 열람에는 게이트가 없다 (G2).", "operationId": "reviewDetail", "parameters": [ { "name": "reviewId", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/ReviewDetailResponse" } } } }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "404": { "description": "- `REVIEW_NOT_FOUND` — 리뷰를 찾을 수 없습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } }, "delete": { "tags": [ "리뷰 상세" ], "summary": "리뷰 삭제", "description": "사진까지 완전 삭제되고 저장으로 되돌아가지 않으며 티켓 1장을 회수한다 (R6·R7). 회수할 티켓이 없으면 409로 거부한다.", "operationId": "deleteReview", "parameters": [ { "name": "reviewId", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "204": { "description": "No Content" }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "401": { "description": "- `UNAUTHORIZED` — 로그인이 필요합니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "404": { "description": "- `REVIEW_NOT_FOUND` — 리뷰를 찾을 수 없습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "409": { "description": "- `REVIEW_DELETE_TICKET_REQUIRED` — 리뷰를 삭제하려면 티켓 1장이 필요합니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } }, "security": [ { "bearerAuth": [] } ] } }, "/v1/review-form-config": { "get": { "tags": [ "리뷰 작성" ], "summary": "리뷰 폼 제약·태그 목록", "description": "폼 제약과 태그 목록을 한 번에 내린다. 화면의 태그 칩은 이 응답으로 그린다.", "operationId": "reviewFormConfig", "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/ReviewFormConfigResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } } }, "/v1/places/{placeId}": { "get": { "tags": [ "가게 상세" ], "summary": "가게 상세", "description": "핀 클릭 시트와 가게 상세 상단이 같은 데이터를 쓴다. 사진은 이 매장 리뷰에서 최신순으로 파생한다 (P7).", "operationId": "placeDetail", "parameters": [ { "name": "placeId", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/PlaceDetailResponse" } } } }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "404": { "description": "- `PLACE_NOT_FOUND` — 매장을 찾을 수 없습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } } }, "/v1/places/{placeId}/reviews": { "get": { "tags": [ "가게 상세" ], "summary": "가게 상세 리뷰 목록", "description": "정렬은 최신순 createdAt DESC, reviewId DESC다.", "operationId": "placeReviews", "parameters": [ { "name": "placeId", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "latitude", "in": "query", "required": false, "schema": { "type": "number", "format": "double" } }, { "name": "longitude", "in": "query", "required": false, "schema": { "type": "number", "format": "double" } }, { "name": "cursor", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32" } } ], "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/CursorPageReviewCardResponse" } } } }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.\n- `INVALID_CURSOR` — 잘못된 커서입니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "404": { "description": "- `PLACE_NOT_FOUND` — 매장을 찾을 수 없습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } } }, "/v1/places/search": { "get": { "tags": [ "매장 검색" ], "summary": "매장 검색", "description": "가게명·주소·음식 카테고리 태그로 찾는다 (E9). 좌표가 오면 거리순, 없으면 매장명 유사도순이다. 결과 0건은 오류가 아니다 — items: []", "operationId": "searchPlaces", "parameters": [ { "name": "query", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "curationTagId", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "latitude", "in": "query", "required": false, "schema": { "type": "number", "format": "double" } }, { "name": "longitude", "in": "query", "required": false, "schema": { "type": "number", "format": "double" } }, { "name": "nearbyOnly", "in": "query", "required": false, "schema": { "type": "boolean" } }, { "name": "cursor", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32" } } ], "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/CursorPagePlaceCardResponse" } } } }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.\n- `INVALID_CURSOR` — 잘못된 커서입니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } } }, "/v1/nearby/reviews": { "get": { "tags": [ "근처 탐색" ], "summary": "피드형 기본 목록", "description": "내 위치 반경 1km 안의 리뷰를 거리순으로 내린다 (E1·E2·E4). 반경은 서버가 고정한다.", "operationId": "nearbyReviews", "parameters": [ { "name": "latitude", "in": "query", "required": true, "schema": { "type": "number", "format": "double" } }, { "name": "longitude", "in": "query", "required": true, "schema": { "type": "number", "format": "double" } }, { "name": "cursor", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32" } } ], "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/CursorPageReviewCardResponse" } } } }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.\n- `INVALID_CURSOR` — 잘못된 커서입니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } } }, "/v1/nearby/places": { "get": { "tags": [ "근처 탐색" ], "summary": "지도형 핀", "description": "커서를 쓰지 않는다 — 지도를 움직이는 것이 페이지 이동이다. 리뷰가 1건 이상 있는 매장만, 최대 30개 (E6·E8).", "operationId": "nearbyPlaces", "parameters": [ { "name": "north", "in": "query", "required": true, "schema": { "type": "number", "format": "double" } }, { "name": "south", "in": "query", "required": true, "schema": { "type": "number", "format": "double" } }, { "name": "east", "in": "query", "required": true, "schema": { "type": "number", "format": "double" } }, { "name": "west", "in": "query", "required": true, "schema": { "type": "number", "format": "double" } }, { "name": "latitude", "in": "query", "required": false, "schema": { "type": "number", "format": "double" } }, { "name": "longitude", "in": "query", "required": false, "schema": { "type": "number", "format": "double" } }, { "name": "query", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "curationTagId", "in": "query", "required": false, "schema": { "type": "string" } } ], "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/NearbyPlacesResponse" } } } }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } } }, "/v1/home": { "get": { "tags": [ "홈" ], "summary": "인사·내 그룹·추천 그룹", "description": "recommendedGroups는 가입 여부와 무관하게 채운다 — 이미 가입한 그룹을 뺀 추천순(G17) 상위 5개.", "operationId": "home", "parameters": [], "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/HomeResponse" } } } }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "401": { "description": "- `UNAUTHORIZED` — 로그인이 필요합니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } }, "security": [ { "bearerAuth": [] } ] } }, "/v1/home/feed": { "get": { "tags": [ "홈" ], "summary": "리뷰 피드", "description": "가입한 그룹들에 공유된 리뷰를 하나로 합쳐 내린다. 같은 리뷰가 여러 그룹에 공유돼 있어도 한 번만 내린다 (G19).", "operationId": "feed", "parameters": [ { "name": "latitude", "in": "query", "required": false, "schema": { "type": "number", "format": "double" } }, { "name": "longitude", "in": "query", "required": false, "schema": { "type": "number", "format": "double" } }, { "name": "cursor", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32" } } ], "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/CursorPageReviewCardResponse" } } } }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.\n- `INVALID_CURSOR` — 잘못된 커서입니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "401": { "description": "- `UNAUTHORIZED` — 로그인이 필요합니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } }, "security": [ { "bearerAuth": [] } ] } }, "/v1/groups/{groupId}/reviews": { "get": { "tags": [ "그룹" ], "summary": "그룹 상세 리뷰 목록", "description": "미가입·비회원도 전체를 커서 페이징으로 본다. 대신 본문과 단점 요약을 서버가 마스킹한다 (G1).", "operationId": "groupReviews", "parameters": [ { "name": "groupId", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "latitude", "in": "query", "required": false, "schema": { "type": "number", "format": "double" } }, { "name": "longitude", "in": "query", "required": false, "schema": { "type": "number", "format": "double" } }, { "name": "cursor", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32" } } ], "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/GatedReviewsResponse" } } } }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.\n- `INVALID_CURSOR` — 잘못된 커서입니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "404": { "description": "- `GROUP_NOT_FOUND` — 그룹을 찾을 수 없습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } } }, "/v1/groups/{groupId}/join-preview": { "get": { "tags": [ "그룹 가입·리뷰 공유" ], "summary": "가입 팝업 정보", "description": "티켓 보유·부족 분기. preview는 참고값이고 가입이 조건을 다시 검증한다 (TX-3).", "operationId": "joinPreview", "parameters": [ { "name": "groupId", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/JoinPreviewResponse" } } } }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "401": { "description": "- `UNAUTHORIZED` — 로그인이 필요합니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "404": { "description": "- `GROUP_NOT_FOUND` — 그룹을 찾을 수 없습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } }, "security": [ { "bearerAuth": [] } ] } }, "/v1/groups/name-availability": { "get": { "tags": [ "그룹" ], "summary": "그룹 이름 중복 확인", "description": "참고값이다 — 생성이 유일성을 다시 검증하고 GROUP_NAME_DUPLICATED로 거절한다.", "operationId": "nameAvailability", "parameters": [ { "name": "name", "in": "query", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/NameAvailabilityResponse" } } } }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "401": { "description": "- `UNAUTHORIZED` — 로그인이 필요합니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } }, "security": [ { "bearerAuth": [] } ] } }, "/v1/group-tags": { "get": { "tags": [ "그룹 태그" ], "summary": "그룹 태그 풀", "description": "음식 카테고리는 단일 선택, 지역은 다중 선택이다 (G7). 서버 상수라 페이징하지 않는다.", "operationId": "groupTags", "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/GroupTagsResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } } }, "/v1/curation-tags": { "get": { "tags": [ "큐레이션" ], "summary": "큐레이션 칩 목록", "description": "칩 값은 서버 상수다. 페이징하지 않으며 값이 바뀌면 서버 배포로 반영된다.", "operationId": "curationTags", "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/ItemsResponseCurationTagResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } } }, "/v1/addresses/search": { "get": { "tags": [ "주소 검색 (mock)" ], "summary": "주소 검색", "description": "addressId는 서명된 불투명 토큰이다. 해석하지 말고 POST /v1/saves의 newPlace.addressId에 그대로 전달한다.\n\n클라이언트는 400ms 디바운스하고, 2자 미만이면 호출하지 않으며, **502를 자동 재시도하지 않는다** (F §2-3).", "operationId": "searchAddresses", "parameters": [ { "name": "query", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "cursor", "in": "query", "required": false, "schema": { "type": "string" } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32" } } ], "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/AddressSearchResponse" } } } }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.\n- `INVALID_CURSOR` — 잘못된 커서입니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "401": { "description": "- `UNAUTHORIZED` — 로그인이 필요합니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "502": { "description": "- `ADDRESS_PROVIDER_UNAVAILABLE` — 주소 서비스가 응답하지 않습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } }, "security": [ { "bearerAuth": [] } ] } }, "/health/db": { "get": { "tags": [ "health-check-controller" ], "operationId": "dbHealth", "responses": { "200": { "description": "DB 연결 정상", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/HealthResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "503": { "description": "DB 연결 실패", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/HealthResponse" } } } } } } }, "/health/api": { "get": { "tags": [ "health-check-controller" ], "operationId": "apiHealth", "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/HealthResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } } }, "/v1/groups/{groupId}/memberships/me": { "delete": { "tags": [ "그룹 가입·리뷰 공유" ], "summary": "탈퇴", "description": "그 그룹에 공유했던 내 리뷰가 전부 내려간다 (G10). 티켓은 돌아오지 않는다 (T9).", "operationId": "leave", "parameters": [ { "name": "groupId", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "204": { "description": "No Content" }, "400": { "description": "- `VALIDATION_FAILED` — 요청 형식이 잘못되었습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "401": { "description": "- `UNAUTHORIZED` — 로그인이 필요합니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "403": { "description": "- `GROUP_MEMBERSHIP_REQUIRED` — 가입하지 않은 그룹입니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "404": { "description": "- `GROUP_NOT_FOUND` — 그룹을 찾을 수 없습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "422": { "description": "- `GROUP_OWNER_CANNOT_LEAVE` — 그룹장은 탈퇴할 수 없습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "500": { "description": "- `INTERNAL_ERROR` — 서버 내부 오류가 발생했습니다.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } }, "security": [ { "bearerAuth": [] } ] } } }, "components": { "schemas": { "NewPlaceRequest": { "type": "object", "properties": { "name": { "type": "string" }, "addressId": { "type": "string" }, "categoryId": { "type": [ "string", "null" ] } }, "required": [ "addressId", "name" ] }, "SaveCreateRequest": { "type": "object", "properties": { "placeId": { "type": [ "string", "null" ] }, "newPlace": { "oneOf": [ { "$ref": "#/components/schemas/NewPlaceRequest" }, { "type": "null" } ] }, "photoAssetIds": { "type": "array", "items": { "type": "string" } }, "companionTagIds": { "type": "array", "items": { "type": "string" } }, "positivePointTagIds": { "type": "array", "items": { "type": "string" } }, "rating": { "type": [ "integer", "null" ], "format": "int32" }, "content": { "type": [ "string", "null" ] } } }, "SaveResultResponse": { "type": "object", "properties": { "saveId": { "type": "string" }, "reviewId": { "type": [ "string", "null" ] }, "placeId": { "type": "string" }, "ticket": { "$ref": "#/components/schemas/TicketGrantSummary" } }, "required": [ "placeId", "saveId", "ticket" ] }, "TicketGrantSummary": { "type": "object", "properties": { "grantedCount": { "type": "integer", "format": "int32" }, "availableCount": { "type": "integer", "format": "int32" } }, "required": [ "availableCount", "grantedCount" ] }, "FavoriteResponse": { "type": "object", "properties": { "placeId": { "type": "string" }, "isFavorite": { "type": "boolean" } }, "required": [ "isFavorite", "placeId" ] }, "GroupRequest": { "type": "object", "properties": { "name": { "type": "string" }, "oneLineDescription": { "type": "string" }, "foodCategoryId": { "type": "string" }, "regionTagIds": { "type": "array", "items": { "type": "string" } }, "imageAssetId": { "type": [ "string", "null" ] }, "description": { "type": [ "string", "null" ] } }, "required": [ "foodCategoryId", "name", "oneLineDescription", "regionTagIds" ] }, "CoverImage": { "type": "object", "properties": { "url": { "type": "string" }, "reviewId": { "type": "string" } }, "required": [ "reviewId", "url" ] }, "FoodCategory": { "type": "object", "properties": { "categoryId": { "type": "string" }, "label": { "type": "string" } }, "required": [ "categoryId", "label" ] }, "GroupDetailResponse": { "type": "object", "properties": { "groupId": { "type": "string" }, "name": { "type": "string" }, "oneLineDescription": { "type": "string" }, "description": { "type": [ "string", "null" ] }, "imageUrl": { "type": [ "string", "null" ] }, "coverImages": { "type": "array", "items": { "$ref": "#/components/schemas/CoverImage" } }, "memberCount": { "type": "integer", "format": "int32" }, "reviewCount": { "type": "integer", "format": "int32" }, "placeCount": { "type": "integer", "format": "int32" }, "foodCategory": { "$ref": "#/components/schemas/FoodCategory" }, "regionTags": { "type": "array", "items": { "$ref": "#/components/schemas/RegionTag" } }, "matchedSavedPlaceCount": { "type": "integer", "format": "int32" }, "isOwner": { "type": "boolean" }, "isMember": { "type": "boolean" } }, "required": [ "coverImages", "foodCategory", "groupId", "isMember", "isOwner", "matchedSavedPlaceCount", "memberCount", "name", "oneLineDescription", "placeCount", "regionTags", "reviewCount" ] }, "RegionTag": { "type": "object", "properties": { "regionTagId": { "type": "string" }, "label": { "type": "string" } }, "required": [ "label", "regionTagId" ] }, "ReplaceSharesRequest": { "type": "object", "properties": { "reviewIds": { "type": "array", "items": { "type": "string" } } }, "required": [ "reviewIds" ] }, "ReplaceSharesResponse": { "type": "object", "properties": { "groupId": { "type": "string" }, "sharedReviewIds": { "type": "array", "items": { "type": "string" } }, "sharedCount": { "type": "integer", "format": "int32" } }, "required": [ "groupId", "sharedCount", "sharedReviewIds" ] }, "RecommendationResponse": { "type": "object", "properties": { "place": { "$ref": "#/components/schemas/RecommendedPlace" }, "summary": { "oneOf": [ { "$ref": "#/components/schemas/RecommendedSummary" }, { "type": "null" } ] } }, "required": [ "place" ] }, "RecommendedPlace": { "type": "object", "properties": { "placeId": { "type": "string" }, "name": { "type": "string" }, "roadAddress": { "type": "string" }, "categoryName": { "type": [ "string", "null" ] }, "thumbnailUrl": { "type": [ "string", "null" ] } }, "required": [ "name", "placeId", "roadAddress" ] }, "RecommendedSummary": { "type": "object", "properties": { "reviewId": { "type": "string" }, "pros": { "type": [ "string", "null" ] }, "cons": { "type": [ "string", "null" ] } }, "required": [ "reviewId" ] }, "UploadIntentRequest": { "type": "object", "properties": { "contentType": { "type": "string" }, "contentLength": { "type": "integer", "format": "int64" } }, "required": [ "contentLength", "contentType" ] }, "UploadIntentResponse": { "type": "object", "properties": { "assetId": { "type": "string" }, "uploadUrl": { "type": "string" }, "expiresAt": { "type": "string" } }, "required": [ "assetId", "expiresAt", "uploadUrl" ] }, "JoinRequest": { "type": "object", "properties": { "sourceReviewId": { "type": [ "string", "null" ], "deprecated": true, "description": "단수 공유 — sourceReviewIds로 대체됐다. 둘 다 오면 합집합 (TMT-241)" }, "sourceReviewIds": { "type": [ "array", "null" ], "description": "가입과 함께 공유할 내 리뷰 목록 — 가입 화면의 체크박스 복수 선택 (TMT-241)", "items": { "type": "string" } } } }, "JoinResponse": { "type": "object", "properties": { "groupId": { "type": "string" }, "joinedAt": { "type": "string" }, "sharedReviewIds": { "type": "array", "items": { "type": "string" } }, "ticket": { "$ref": "#/components/schemas/TicketConsumeSummary" } }, "required": [ "groupId", "joinedAt", "sharedReviewIds", "ticket" ] }, "TicketConsumeSummary": { "type": "object", "properties": { "consumedCount": { "type": "integer", "format": "int32" }, "availableCount": { "type": "integer", "format": "int32" } }, "required": [ "availableCount", "consumedCount" ] }, "TokenRefreshRequest": { "type": "object", "properties": { "refreshToken": { "type": "string", "minLength": 1 } }, "required": [ "refreshToken" ] }, "TokenRefreshResponse": { "type": "object", "properties": { "accessToken": { "type": "string" }, "accessTokenExpiresIn": { "type": "integer", "format": "int64", "description": "accessToken 만료까지 남은 초" }, "refreshToken": { "type": "string" } }, "required": [ "accessToken", "accessTokenExpiresIn", "refreshToken" ] }, "KakaoLoginRequest": { "type": "object", "properties": { "code": { "type": "string", "description": "카카오 인가 코드. 1회용이라 재사용하면 AUTH_KAKAO_CODE_INVALID다", "minLength": 1 }, "redirectUri": { "type": "string", "description": "인가 요청에 사용한 리다이렉트 URI", "example": "http://localhost:3000/auth/kakao/callback", "minLength": 1 } }, "required": [ "code", "redirectUri" ] }, "KakaoLoginResponse": { "type": "object", "properties": { "userId": { "type": "string", "description": "사용자 ID 표기 (`user_7`). 다른 응답의 userId와 같은 형식이다", "example": "user_7" }, "nickname": { "type": "string" }, "profileImageUrl": { "type": [ "string", "null" ] }, "accessToken": { "type": "string" }, "accessTokenExpiresIn": { "type": "integer", "format": "int64", "description": "accessToken 만료까지 남은 초" }, "refreshToken": { "type": "string" }, "isNewUser": { "type": "boolean" } }, "required": [ "accessToken", "accessTokenExpiresIn", "isNewUser", "nickname", "refreshToken", "userId" ] }, "UserProfileResponse": { "type": "object", "properties": { "userId": { "type": "string" }, "nickname": { "type": "string" }, "profileImageUrl": { "type": [ "string", "null" ] }, "reviewCount": { "type": "integer", "format": "int32" }, "joinedGroupCount": { "type": "integer", "format": "int32" }, "favoritePlaceCount": { "type": "integer", "format": "int32" } }, "required": [ "favoritePlaceCount", "joinedGroupCount", "nickname", "reviewCount", "userId" ] }, "CursorPageUserReviewGridItem": { "type": "object", "description": "커서 기반 페이지 응답", "properties": { "items": { "type": "array", "description": "결과 목록. 없으면 빈 배열", "items": { "$ref": "#/components/schemas/UserReviewGridItem" } }, "nextCursor": { "type": [ "string", "null" ], "description": "다음 페이지 요청에 그대로 전달. 마지막 페이지면 null" }, "hasNext": { "type": "boolean", "description": "다음 페이지 존재 여부", "example": true } }, "required": [ "hasNext", "items" ] }, "ReviewGridPlace": { "type": "object", "properties": { "placeId": { "type": "string" }, "name": { "type": "string" }, "categoryName": { "type": [ "string", "null" ] } }, "required": [ "name", "placeId" ] }, "UserReviewGridItem": { "type": "object", "properties": { "reviewId": { "type": "string" }, "thumbnailUrl": { "type": [ "string", "null" ] }, "place": { "$ref": "#/components/schemas/ReviewGridPlace" }, "createdAt": { "type": "string" } }, "required": [ "createdAt", "place", "reviewId" ] }, "CursorPageGroupCardResponse": { "type": "object", "description": "커서 기반 페이지 응답", "properties": { "items": { "type": "array", "description": "결과 목록. 없으면 빈 배열", "items": { "$ref": "#/components/schemas/GroupCardResponse" } }, "nextCursor": { "type": [ "string", "null" ], "description": "다음 페이지 요청에 그대로 전달. 마지막 페이지면 null" }, "hasNext": { "type": "boolean", "description": "다음 페이지 존재 여부", "example": true } }, "required": [ "hasNext", "items" ] }, "GroupCardResponse": { "type": "object", "properties": { "groupId": { "type": "string" }, "name": { "type": "string" }, "oneLineDescription": { "type": "string" }, "coverImageUrl": { "type": [ "string", "null" ] }, "memberCount": { "type": "integer", "format": "int32" }, "reviewCount": { "type": "integer", "format": "int32" }, "placeCount": { "type": "integer", "format": "int32" }, "matchedSavedPlaceCount": { "type": "integer", "format": "int32" } }, "required": [ "groupId", "matchedSavedPlaceCount", "memberCount", "name", "oneLineDescription", "placeCount", "reviewCount" ] }, "CursorPagePlaceCardResponse": { "type": "object", "description": "커서 기반 페이지 응답", "properties": { "items": { "type": "array", "description": "결과 목록. 없으면 빈 배열", "items": { "$ref": "#/components/schemas/PlaceCardResponse" } }, "nextCursor": { "type": [ "string", "null" ], "description": "다음 페이지 요청에 그대로 전달. 마지막 페이지면 null" }, "hasNext": { "type": "boolean", "description": "다음 페이지 존재 여부", "example": true } }, "required": [ "hasNext", "items" ] }, "PlaceCardResponse": { "type": "object", "properties": { "placeId": { "type": "string" }, "name": { "type": "string" }, "roadAddress": { "type": "string" }, "regionName": { "type": "string" }, "categoryId": { "type": [ "string", "null" ] }, "categoryName": { "type": [ "string", "null" ] }, "averageRating": { "type": [ "number", "null" ], "format": "double" }, "reviewCount": { "type": "integer", "format": "int32" }, "thumbnailUrl": { "type": [ "string", "null" ] }, "distanceMeters": { "type": [ "integer", "null" ], "format": "int32" }, "isFavorite": { "type": "boolean" } }, "required": [ "isFavorite", "name", "placeId", "regionName", "reviewCount", "roadAddress" ] }, "MyProfileResponse": { "type": "object", "properties": { "userId": { "type": "string" }, "nickname": { "type": "string" }, "email": { "type": [ "string", "null" ] }, "profileImageUrl": { "type": [ "string", "null" ] }, "availableTicketCount": { "type": "integer", "format": "int32" }, "reviewCount": { "type": "integer", "format": "int32" }, "joinedGroupCount": { "type": "integer", "format": "int32" }, "favoritePlaceCount": { "type": "integer", "format": "int32" } }, "required": [ "availableTicketCount", "favoritePlaceCount", "joinedGroupCount", "nickname", "reviewCount", "userId" ] }, "GroupRef": { "type": "object", "properties": { "groupId": { "type": "string" }, "name": { "type": "string" } }, "required": [ "groupId", "name" ] }, "PlaceRef": { "type": "object", "properties": { "placeId": { "type": "string" }, "name": { "type": "string" }, "roadAddress": { "type": "string" } }, "required": [ "name", "placeId", "roadAddress" ] }, "TicketHistoryItem": { "type": "object", "properties": { "entryId": { "type": "string" }, "type": { "type": "string", "enum": [ "SIGNUP_REWARD", "REVIEW_REWARD", "REVIEW_DELETE_REVOKE", "GROUP_JOIN" ] }, "amount": { "type": "integer", "format": "int32" }, "saveId": { "type": [ "string", "null" ] }, "place": { "oneOf": [ { "$ref": "#/components/schemas/PlaceRef" }, { "type": "null" } ] }, "group": { "oneOf": [ { "$ref": "#/components/schemas/GroupRef" }, { "type": "null" } ] }, "occurredAt": { "type": "string" } }, "required": [ "amount", "entryId", "occurredAt", "type" ] }, "TicketHistoryResponse": { "type": "object", "properties": { "availableCount": { "type": "integer", "format": "int32" }, "inProgressSaveCount": { "type": "integer", "format": "int32" }, "items": { "type": "array", "items": { "$ref": "#/components/schemas/TicketHistoryItem" } }, "nextCursor": { "type": [ "string", "null" ] }, "hasNext": { "type": "boolean" } }, "required": [ "availableCount", "hasNext", "inProgressSaveCount", "items" ] }, "CursorPageMyReviewGridItem": { "type": "object", "description": "커서 기반 페이지 응답", "properties": { "items": { "type": "array", "description": "결과 목록. 없으면 빈 배열", "items": { "$ref": "#/components/schemas/MyReviewGridItem" } }, "nextCursor": { "type": [ "string", "null" ], "description": "다음 페이지 요청에 그대로 전달. 마지막 페이지면 null" }, "hasNext": { "type": "boolean", "description": "다음 페이지 존재 여부", "example": true } }, "required": [ "hasNext", "items" ] }, "MyReviewGridItem": { "type": "object", "properties": { "reviewId": { "type": "string" }, "saveId": { "type": "string" }, "thumbnailUrl": { "type": [ "string", "null" ] }, "place": { "$ref": "#/components/schemas/ReviewGridPlace" }, "createdAt": { "type": "string" } }, "required": [ "createdAt", "place", "reviewId", "saveId" ] }, "CursorPageSaveListItemResponse": { "type": "object", "description": "커서 기반 페이지 응답", "properties": { "items": { "type": "array", "description": "결과 목록. 없으면 빈 배열", "items": { "$ref": "#/components/schemas/SaveListItemResponse" } }, "nextCursor": { "type": [ "string", "null" ], "description": "다음 페이지 요청에 그대로 전달. 마지막 페이지면 null" }, "hasNext": { "type": "boolean", "description": "다음 페이지 존재 여부", "example": true } }, "required": [ "hasNext", "items" ] }, "PlaceAddressSummary": { "type": "object", "properties": { "placeId": { "type": "string" }, "name": { "type": "string" }, "roadAddress": { "type": "string" } }, "required": [ "name", "placeId", "roadAddress" ] }, "SaveListItemResponse": { "type": "object", "properties": { "saveId": { "type": "string" }, "place": { "$ref": "#/components/schemas/PlaceAddressSummary" }, "thumbnailUrl": { "type": [ "string", "null" ] }, "updatedAt": { "type": "string" } }, "required": [ "place", "saveId", "updatedAt" ] }, "AiSummaryResponse": { "type": "object", "properties": { "pros": { "type": [ "string", "null" ] }, "cons": { "type": [ "string", "null" ] } } }, "Photo": { "type": "object", "properties": { "photoId": { "type": "string" }, "url": { "type": "string" }, "order": { "type": "integer", "format": "int32" } }, "required": [ "order", "photoId", "url" ] }, "PlaceSummary": { "type": "object", "properties": { "placeId": { "type": "string" }, "name": { "type": "string" }, "roadAddress": { "type": "string" }, "categoryName": { "type": [ "string", "null" ] } }, "required": [ "name", "placeId", "roadAddress" ] }, "SaveDetailResponse": { "type": "object", "properties": { "saveId": { "type": "string" }, "reviewId": { "type": [ "string", "null" ] }, "place": { "$ref": "#/components/schemas/PlaceSummary" }, "photos": { "type": "array", "items": { "$ref": "#/components/schemas/Photo" } }, "tags": { "type": "array", "items": { "$ref": "#/components/schemas/TagResponse" } }, "rating": { "type": [ "integer", "null" ], "format": "int32" }, "content": { "type": [ "string", "null" ] }, "aiSummary": { "oneOf": [ { "$ref": "#/components/schemas/AiSummaryResponse" }, { "type": "null" } ] }, "createdAt": { "type": "string" } }, "required": [ "createdAt", "photos", "place", "saveId", "tags" ] }, "TagResponse": { "type": "object", "properties": { "tagId": { "type": "string" }, "label": { "type": "string" } }, "required": [ "label", "tagId" ] }, "AiSummary": { "type": "object", "properties": { "pros": { "type": [ "string", "null" ] }, "cons": { "type": [ "string", "null" ] } } }, "Author": { "type": "object", "properties": { "userId": { "type": "string" }, "nickname": { "type": "string" }, "profileImageUrl": { "type": [ "string", "null" ] } }, "required": [ "nickname", "userId" ] }, "ReviewDetailResponse": { "type": "object", "properties": { "reviewId": { "type": "string" }, "author": { "$ref": "#/components/schemas/Author" }, "place": { "$ref": "#/components/schemas/PlaceSummary" }, "photos": { "type": "array", "items": { "$ref": "#/components/schemas/Photo" } }, "tags": { "type": "array", "items": { "$ref": "#/components/schemas/Tag" } }, "rating": { "type": "integer", "format": "int32" }, "content": { "type": "string" }, "aiSummary": { "oneOf": [ { "$ref": "#/components/schemas/AiSummary" }, { "type": "null" } ] }, "createdAt": { "type": "string" }, "isMine": { "type": "boolean" } }, "required": [ "author", "content", "createdAt", "isMine", "photos", "place", "rating", "reviewId", "tags" ] }, "Tag": { "type": "object", "properties": { "tagId": { "type": "string" }, "label": { "type": "string" } }, "required": [ "label", "tagId" ] }, "ContentConstraint": { "type": "object", "properties": { "maxLength": { "type": "integer", "format": "int32" } }, "required": [ "maxLength" ] }, "PhotoConstraint": { "type": "object", "properties": { "maxCount": { "type": "integer", "format": "int32" }, "maxBytes": { "type": "integer", "format": "int64" }, "allowedContentTypes": { "type": "array", "items": { "type": "string" } } }, "required": [ "allowedContentTypes", "maxBytes", "maxCount" ] }, "RatingConstraint": { "type": "object", "properties": { "min": { "type": "integer", "format": "int32" }, "max": { "type": "integer", "format": "int32" }, "step": { "type": "integer", "format": "int32" } }, "required": [ "max", "min", "step" ] }, "ReviewFormConfigResponse": { "type": "object", "properties": { "photo": { "$ref": "#/components/schemas/PhotoConstraint" }, "rating": { "$ref": "#/components/schemas/RatingConstraint" }, "content": { "$ref": "#/components/schemas/ContentConstraint" }, "companionTags": { "type": "array", "items": { "$ref": "#/components/schemas/TagDefinition" } }, "positivePointTags": { "type": "array", "items": { "$ref": "#/components/schemas/TagDefinition" } } }, "required": [ "companionTags", "content", "photo", "positivePointTags", "rating" ] }, "TagDefinition": { "type": "object", "properties": { "tagId": { "type": "string" }, "label": { "type": "string" } }, "required": [ "label", "tagId" ] }, "PlaceDetailResponse": { "type": "object", "properties": { "placeId": { "type": "string" }, "name": { "type": "string" }, "categoryId": { "type": [ "string", "null" ] }, "categoryName": { "type": [ "string", "null" ] }, "averageRating": { "type": [ "number", "null" ], "format": "double" }, "reviewCount": { "type": "integer", "format": "int32" }, "photos": { "type": "array", "items": { "$ref": "#/components/schemas/PlacePhoto" } }, "roadAddress": { "type": "string" }, "latitude": { "type": "number", "format": "double" }, "longitude": { "type": "number", "format": "double" }, "phoneNumber": { "type": [ "string", "null" ] }, "isFavorite": { "type": "boolean" } }, "required": [ "isFavorite", "latitude", "longitude", "name", "photos", "placeId", "reviewCount", "roadAddress" ] }, "PlacePhoto": { "type": "object", "properties": { "url": { "type": "string" }, "reviewId": { "type": "string" } }, "required": [ "reviewId", "url" ] }, "CursorPageReviewCardResponse": { "type": "object", "description": "커서 기반 페이지 응답", "properties": { "items": { "type": "array", "description": "결과 목록. 없으면 빈 배열", "items": { "$ref": "#/components/schemas/ReviewCardResponse" } }, "nextCursor": { "type": [ "string", "null" ], "description": "다음 페이지 요청에 그대로 전달. 마지막 페이지면 null" }, "hasNext": { "type": "boolean", "description": "다음 페이지 존재 여부", "example": true } }, "required": [ "hasNext", "items" ] }, "PlaceRegionSummary": { "type": "object", "properties": { "placeId": { "type": "string" }, "name": { "type": "string" }, "regionName": { "type": "string" }, "categoryId": { "type": [ "string", "null" ] }, "categoryName": { "type": [ "string", "null" ] }, "isFavorite": { "type": "boolean" } }, "required": [ "isFavorite", "name", "placeId", "regionName" ] }, "ReviewCardResponse": { "type": "object", "properties": { "reviewId": { "type": "string" }, "author": { "$ref": "#/components/schemas/Author" }, "rating": { "type": "integer", "format": "int32" }, "distanceMeters": { "type": [ "integer", "null" ], "format": "int32" }, "photos": { "type": "array", "items": { "$ref": "#/components/schemas/Photo" } }, "aiSummary": { "oneOf": [ { "$ref": "#/components/schemas/AiSummary" }, { "type": "null" } ] }, "content": { "type": [ "string", "null" ] }, "contentLength": { "type": "integer", "format": "int32" }, "tags": { "type": "array", "items": { "$ref": "#/components/schemas/Tag" } }, "place": { "$ref": "#/components/schemas/PlaceRegionSummary" }, "createdAt": { "type": "string" } }, "required": [ "author", "contentLength", "createdAt", "photos", "place", "rating", "reviewId", "tags" ] }, "NearbyPlacesResponse": { "type": "object", "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/Pin" } }, "truncated": { "type": "boolean" } }, "required": [ "items", "truncated" ] }, "Pin": { "type": "object", "properties": { "placeId": { "type": "string" }, "name": { "type": "string" }, "latitude": { "type": "number", "format": "double" }, "longitude": { "type": "number", "format": "double" }, "categoryId": { "type": [ "string", "null" ] }, "reviewCount": { "type": "integer", "format": "int32" } }, "required": [ "latitude", "longitude", "name", "placeId", "reviewCount" ] }, "HomeResponse": { "type": "object", "properties": { "nickname": { "type": "string" }, "myGroups": { "type": "array", "items": { "$ref": "#/components/schemas/MyGroup" } }, "recommendedGroups": { "type": "array", "items": { "$ref": "#/components/schemas/GroupCardResponse" } } }, "required": [ "myGroups", "nickname", "recommendedGroups" ] }, "MyGroup": { "type": "object", "properties": { "groupId": { "type": "string" }, "name": { "type": "string" }, "imageUrl": { "type": [ "string", "null" ] } }, "required": [ "groupId", "name" ] }, "Gate": { "type": "object", "properties": { "gated": { "type": "boolean" }, "reason": { "type": [ "string", "null" ] } }, "required": [ "gated" ] }, "GatedReviewsResponse": { "type": "object", "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/ReviewCardResponse" } }, "gate": { "$ref": "#/components/schemas/Gate" }, "nextCursor": { "type": [ "string", "null" ] }, "hasNext": { "type": "boolean" } }, "required": [ "gate", "hasNext", "items" ] }, "Item": { "type": "object", "properties": { "reviewId": { "type": "string" }, "placeName": { "type": "string" }, "thumbnailUrl": { "type": "string" }, "contentPreview": { "type": "string" }, "createdAt": { "type": "string" }, "isShared": { "type": "boolean" } }, "required": [ "contentPreview", "createdAt", "isShared", "placeName", "reviewId", "thumbnailUrl" ] }, "ReviewSharesResponse": { "type": "object", "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/Item" } }, "sharedCount": { "type": "integer", "format": "int32" }, "nextCursor": { "type": [ "string", "null" ] }, "hasNext": { "type": "boolean" } }, "required": [ "hasNext", "items", "sharedCount" ] }, "GroupSummary": { "type": "object", "properties": { "groupId": { "type": "string" }, "name": { "type": "string" }, "imageUrl": { "type": [ "string", "null" ] } }, "required": [ "groupId", "name" ] }, "JoinPreviewResponse": { "type": "object", "properties": { "group": { "$ref": "#/components/schemas/GroupSummary" }, "availableTicketCount": { "type": "integer", "format": "int32" }, "requiredTicketCount": { "type": "integer", "format": "int32" }, "joinable": { "type": "boolean" }, "blockedReason": { "type": [ "string", "null" ] } }, "required": [ "availableTicketCount", "group", "joinable", "requiredTicketCount" ] }, "NameAvailabilityResponse": { "type": "object", "properties": { "name": { "type": "string" }, "available": { "type": "boolean" } }, "required": [ "available", "name" ] }, "GroupTagsResponse": { "type": "object", "properties": { "foodCategories": { "type": "array", "items": { "$ref": "#/components/schemas/FoodCategory" } }, "regionTags": { "type": "array", "items": { "$ref": "#/components/schemas/RegionTag" } } }, "required": [ "foodCategories", "regionTags" ] }, "CurationTagResponse": { "type": "object", "description": "큐레이션 칩", "properties": { "curationTagId": { "type": "string", "description": "칩 식별자. 검색(placesSearch)의 curationTagId로 그대로 전달", "example": "curation_euljiro_yajang" }, "label": { "type": "string", "description": "화면 노출 문구", "example": "을지로 야장" } }, "required": [ "curationTagId", "label" ] }, "ItemsResponseCurationTagResponse": { "type": "object", "description": "페이징 없는 목록 응답", "properties": { "items": { "type": "array", "description": "결과 목록. 없으면 빈 배열", "items": { "$ref": "#/components/schemas/CurationTagResponse" } } }, "required": [ "items" ] }, "AddressItem": { "type": "object", "properties": { "addressId": { "type": "string", "description": "불투명 토큰. 해석하지 말고 그대로 전달한다" }, "roadAddress": { "type": "string" }, "jibunAddress": { "type": [ "string", "null" ], "description": "juso가 주지 않는 경우가 있다" }, "regionName": { "type": "string", "description": "시군구명 + 읍면동명", "example": "양천구 신정동" } }, "required": [ "addressId", "regionName", "roadAddress" ] }, "AddressSearchResponse": { "type": "object", "description": "주소 검색 결과. truncated 때문에 공용 CursorPage가 아닌 전용 타입이다", "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/AddressItem" } }, "nextCursor": { "type": [ "string", "null" ] }, "hasNext": { "type": "boolean" }, "truncated": { "type": "boolean", "description": "페이지 상한에 걸려 더 내리지 않는다는 뜻. 화면은 검색어를 더 구체적으로 안내한다" } }, "required": [ "hasNext", "items", "truncated" ] }, "HealthResponse": { "type": "object", "properties": { "status": { "type": "string" }, "service": { "type": "string" } }, "required": [ "service", "status" ] }, "ErrorResponse": { "type": "object", "description": "실패 응답 (RFC 9457 Problem Details)", "properties": { "title": { "type": "string", "description": "사용자에게 보여줄 수 있는 문구", "example": "요청 경로가 잘못되었습니다." }, "status": { "type": "integer", "format": "int32", "description": "HTTP 상태 코드", "example": 404 }, "detail": { "type": [ "string", "null" ], "description": "상황별 상세" }, "code": { "type": "string", "description": "오류 식별자. 클라이언트 분기의 기준", "example": "RESOURCE_NOT_FOUND" }, "timestamp": { "type": "string", "format": "date-time", "description": "발생 시각 (ISO-8601 UTC)", "example": "2026-08-03T05:12:44.831Z" }, "requestId": { "type": "string", "description": "서버 로그 추적용", "example": "8f1e0c4a-2c37-4d1e-9f6b-2f0a1c9d3e55" } }, "required": [ "code", "requestId", "status", "timestamp", "title" ] } }, "securitySchemes": { "bearerAuth": { "type": "http", "scheme": "bearer", "bearerFormat": "JWT" } } } }