Swagger UI: http://localhost:8080/swagger-ui/index.html
이 문서는 전체 API 목록과 에러 코드를 정리한 참고용 문서입니다. 실시간 API 스펙은 Swagger UI에서 확인하세요.
개발용 테스트 데이터가 필요하면 .env 값을 주입한 뒤 서버를 먼저 실행하세요. 서버가 한 번 떠서 Flyway가 테이블을 만든 다음 seed 데이터를 넣습니다.
set -a
source .env
set +a
./gradlew bootRun다른 터미널에서 seed 데이터를 적용합니다.
./scripts/local-test-data.sh샘플 계정:
demo-user01@todaybread.com~demo-user20@todaybread.com/todaybread123demo-boss001@todaybread.com~demo-boss120@todaybread.com/todaybread123
근처 매장/빵 조회 추천 좌표:
- 한성대학교:
lat=37.5826000,lng=127.0106000,radius=1,3,5
이미지 URL은 프로필별 저장소에 따라 달라집니다. 로컬/test 프로필은 /images/{storedFilename}, EC2 프로필은 S3 public URL을 내려줍니다. 예시는 로컬 기준 설명용 파일명이며, 실제 seed 데이터에서는 seed_store_01_store_001.png, seed_bread_01_bread_1.jpeg, seed_review_01_review_1_1.jpeg 같은 저장 파일명이 내려옵니다.
- 인증
- 사용자
- 계정 복구
- 빵 — 일반 유저
- 빵 — 사장님
- 매장 — 일반 유저
- 매장 — 사장님
- 주문 — 사장님
- 매출 — 사장님
- 키워드
- 관심지역
- 단골 가게
- 찜목록
- 장바구니
- 주문
- 결제
- 리뷰
- 시스템
- 인증 구조
- 공통 에러 응답 형식
- 에러 코드
| 항목 | 값 |
|---|---|
| 인증 | X |
요청 바디:
{
"refreshToken": "eyJhbGciOiJIUzI1NiJ9..."
}응답 형식:
{
"accessToken": "eyJhbGciOiJIUzI1NiJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiJ9..."
}에러 응답: AUTH_003
| 항목 | 값 |
|---|---|
| 인증 | O |
응답 형식:
{
"success": true
}에러 응답: AUTH_001, AUTH_002
| 항목 | 값 |
|---|---|
| 인증 | X |
요청 바디:
{
"email": "user@todaybread.com",
"nickname": "빵순이",
"name": "김빵순",
"password": "todaybread123",
"phoneNumber": "010-1234-5678"
}응답 형식:
{
"success": true,
"message": "회원가입 완료"
}에러 응답: USER_001, USER_002, USER_003, COMMON_001
| 항목 | 값 |
|---|---|
| 인증 | X |
요청 바디:
{
"email": "demo-user01@todaybread.com",
"password": "todaybread123"
}응답 형식:
{
"success": true,
"accessToken": "eyJhbGciOiJIUzI1NiJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiJ9...",
"nickname": "demo-user",
"name": "데모 유저",
"phoneNumber": "010-7000-1001"
}에러 응답: USER_004, COMMON_001
| 항목 | 값 |
|---|---|
| 인증 | X |
쿼리 파라미터: value (이메일)
응답 형식:
true
true: 이미 존재,false: 사용 가능
| 항목 | 값 |
|---|---|
| 인증 | X |
쿼리 파라미터: value (닉네임)
응답 형식:
false| 항목 | 값 |
|---|---|
| 인증 | X |
쿼리 파라미터: value (전화번호)
응답 형식:
false| 항목 | 값 |
|---|---|
| 인증 | O |
요청 바디:
{
"nickname": "빵덕후",
"name": "김빵순",
"phoneNumber": "010-9876-5432"
}응답 형식:
{
"nickname": "빵덕후",
"name": "김빵순",
"phoneNumber": "010-9876-5432"
}에러 응답: USER_002, USER_003, USER_004, COMMON_001
| 항목 | 값 |
|---|---|
| 인증 | O |
요청 바디:
{
"bossNumber": "1234567890",
"businessStartDate": "20260101",
"representativeName": "김한성"
}응답 형식:
{
"success": true,
"message": "사업자 등록이 완료되었습니다.",
"accessToken": "eyJhbGciOiJIUzI1NiJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiJ9..."
}사장님 등록 후 역할이 BOSS로 변경되므로 새 토큰이 발급됩니다.
에러 응답: USER_004, USER_006, USER_007, USER_009, USER_010, USER_011, USER_012, COMMON_001
아래 API는 인증 없이 접근 가능합니다.
verify-identity성공 시 10분 유효 일회용resetToken이 발급되고,reset-password는 해당 토큰의 존재/만료/userId 일치를 검증한 뒤 비밀번호를 변경합니다. 단, recovery 엔드포인트 자체의 Rate Limiting은 별도 구현되어 있지 않습니다.
| 항목 | 값 |
|---|---|
| 인증 | X |
쿼리 파라미터: phone (전화번호)
응답 형식:
{
"maskedEmail": "de****@todaybread.com"
}에러 응답: USER_005, COMMON_001
| 항목 | 값 |
|---|---|
| 인증 | X |
쿼리 파라미터: phone (전화번호), email (이메일)
응답 형식:
{
"verified": true,
"email": "demo-user01@todaybread.com",
"resetToken": "550e8400-e29b-41d4-a716-446655440000"
}에러 응답: USER_005, COMMON_001
| 항목 | 값 |
|---|---|
| 인증 | X |
요청 바디:
{
"email": "demo-user01@todaybread.com",
"newPassword": "newpassword123",
"resetToken": "550e8400-e29b-41d4-a716-446655440000"
}응답 형식:
{
"success": true,
"message": "비밀번호가 재설정되었습니다."
}비밀번호 재설정 시 기존 세션(Refresh Token)이 모두 무효화됩니다.
에러 응답: USER_005, USER_008, COMMON_001
| 항목 | 값 |
|---|---|
| 인증 | O |
쿼리 파라미터:
| 파라미터 | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
lat |
BigDecimal | O | — | 위도 (-90 ~ 90) |
lng |
BigDecimal | O | — | 경도 (-180 ~ 180) |
radius |
int | X | 1 | 검색 반경 km (1 ~ 10) |
sort |
String | X | none | 정렬: none(랜덤 셔플), distance, price, discount |
응답 형식:
[
{
"id": 1,
"name": "시그니처 소금빵",
"originalPrice": 3500,
"salePrice": 2500,
"imageUrl": "/images/bread_1_abc123.jpg",
"storeId": 1,
"storeName": "투데이브레드 데모 강남점",
"isSelling": true,
"lastOrderTime": "22:30:00",
"distance": 0.35,
"averageRating": 4.5,
"reviewCount": 12
}
]
sort=none(기본값)은 의도적으로 랜덤 셔플합니다. 품절된 빵은 목록에서 제외됩니다.
에러 응답: COMMON_001
| 항목 | 값 |
|---|---|
| 인증 | O |
경로 변수: breadId (빵 ID)
응답 형식:
{
"id": 1,
"name": "시그니처 소금빵",
"originalPrice": 3500,
"salePrice": 2500,
"remainingQuantity": 14,
"description": "겉은 바삭하고 속은 촉촉한 대표 메뉴입니다.",
"imageUrl": "/images/bread_1_abc123.jpg",
"storeId": 1,
"storeName": "투데이브레드 데모 강남점",
"isSelling": true,
"averageRating": 4.5,
"reviewCount": 12
}에러 응답: BREAD_001
| 항목 | 값 |
|---|---|
| 인증 | O |
경로 변수: storeId (가게 ID)
응답 형식:
[
{
"id": 1,
"storeId": 1,
"name": "시그니처 소금빵",
"originalPrice": 3500,
"salePrice": 2500,
"remainingQuantity": 14,
"description": "겉은 바삭하고 속은 촉촉한 대표 메뉴입니다.",
"imageUrl": "/images/bread_1_abc123.jpg"
}
]에러 응답: STORE_004
| 항목 | 값 |
|---|---|
| 인증 | O |
| 권한 | BOSS |
응답 형식:
[
{
"id": 1,
"storeId": 1,
"name": "시그니처 소금빵",
"originalPrice": 3500,
"salePrice": 2500,
"remainingQuantity": 14,
"description": "겉은 바삭하고 속은 촉촉한 대표 메뉴입니다.",
"imageUrl": "/images/bread_1_abc123.jpg"
}
]에러 응답: STORE_001, STORE_004
| 항목 | 값 |
|---|---|
| 인증 | O |
| 권한 | BOSS |
| Content-Type | multipart/form-data |
요청 파트:
request(JSON):
{
"name": "크림치즈 베이글",
"originalPrice": 4000,
"salePrice": 2800,
"remainingQuantity": 10,
"description": "부드러운 크림치즈가 듬뿍 들어간 베이글입니다."
}image(파일, 선택): 빵 이미지
응답 형식:
{
"id": 4,
"storeId": 1,
"name": "크림치즈 베이글",
"originalPrice": 4000,
"salePrice": 2800,
"remainingQuantity": 10,
"description": "부드러운 크림치즈가 듬뿍 들어간 베이글입니다.",
"imageUrl": "/images/bread_4_abc123.jpg"
}에러 응답: STORE_001, STORE_004, BREAD_004, COMMON_001, COMMON_005, COMMON_006, COMMON_007
| 항목 | 값 |
|---|---|
| 인증 | O |
| 권한 | BOSS |
| Content-Type | multipart/form-data |
경로 변수: breadId (빵 ID)
요청 파트:
request(JSON):
{
"name": "크림치즈 베이글",
"originalPrice": 4500,
"salePrice": 3000,
"remainingQuantity": 15,
"description": "리뉴얼! 크림치즈가 더 듬뿍."
}image(파일, 선택): 새 이미지
응답 형식:
{
"id": 4,
"storeId": 1,
"name": "크림치즈 베이글",
"originalPrice": 4500,
"salePrice": 3000,
"remainingQuantity": 15,
"description": "리뉴얼! 크림치즈가 더 듬뿍.",
"imageUrl": "/images/bread_4_abc123.jpg"
}에러 응답: BREAD_001, BREAD_002, BREAD_004, COMMON_001, COMMON_005, COMMON_006, COMMON_007
| 항목 | 값 |
|---|---|
| 인증 | O |
| 권한 | BOSS |
경로 변수: breadId (빵 ID)
요청 바디:
{
"remainingQuantity": 0
}
remainingQuantity를 0으로 설정하면 품절 처리됩니다.
응답 형식:
{
"success": true
}에러 응답: BREAD_001, BREAD_002, COMMON_001
| 항목 | 값 |
|---|---|
| 인증 | O |
| 권한 | BOSS |
경로 변수: breadId (빵 ID)
응답 형식:
{
"success": true
}메뉴 삭제는 Soft Delete입니다. 실제
bread레코드와 빵 이미지는 삭제하지 않고, 일반 목록/상세/장바구니/신규 주문에서는 삭제된 빵을 노출하거나 선택할 수 없게 처리합니다. 기존 주문 항목, 리뷰, 빵 이미지 조회에 필요한 데이터는 유지됩니다.
에러 응답: BREAD_001, BREAD_002
| 항목 | 값 |
|---|---|
| 인증 | O |
쿼리 파라미터:
| 파라미터 | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
lat |
BigDecimal | O | — | 위도 (-90 ~ 90) |
lng |
BigDecimal | O | — | 경도 (-180 ~ 180) |
radius |
int | X | 1 | 검색 반경 km (1 ~ 10) |
응답 형식:
[
{
"storeId": 1,
"name": "투데이브레드 데모 강남점",
"storeAddressLine1": "서울특별시 강남구 테헤란로 123",
"storeAddressLine2": "1층",
"latitude": 37.5826000,
"longitude": 127.0106000,
"primaryImageUrl": "/images/store_1_abc123.jpg",
"isSelling": true,
"distance": 0.35,
"lastOrderTime": "22:30:00",
"averageRating": 4.5,
"reviewCount": 12
}
]에러 응답: COMMON_001
| 항목 | 값 |
|---|---|
| 인증 | O |
경로 변수: storeId (가게 ID)
응답 형식:
{
"store": {
"id": 1,
"name": "투데이브레드 데모 강남점",
"phone": "02-7000-3001",
"description": "강남역 근처에서 소금빵과 식사용 빵을 판매하는 프론트 연동용 데모 매장입니다.",
"addressLine1": "서울특별시 강남구 테헤란로 123",
"addressLine2": "1층",
"latitude": 37.5826000,
"longitude": 127.0106000,
"businessHours": [
{
"dayOfWeek": 1,
"isClosed": false,
"startTime": "07:00:00",
"endTime": "23:00:00",
"lastOrderTime": "22:30:00"
},
{
"dayOfWeek": 7,
"isClosed": true,
"startTime": null,
"endTime": null,
"lastOrderTime": null
}
]
},
"images": [
{
"id": 1,
"imageUrl": "/images/store_1_abc123.jpg",
"displayOrder": 0
}
],
"breads": [
{
"id": 1,
"storeId": 1,
"name": "시그니처 소금빵",
"originalPrice": 3500,
"salePrice": 2500,
"remainingQuantity": 14,
"description": "겉은 바삭하고 속은 촉촉한 대표 메뉴입니다.",
"imageUrl": "/images/bread_1_abc123.jpg"
}
],
"isSelling": true,
"averageRating": 4.5,
"reviewCount": 12
}에러 응답: STORE_004
| 항목 | 값 |
|---|---|
| 인증 | O |
| 권한 | BOSS |
응답 형식:
{
"hasStore": true
}에러 응답: STORE_001
| 항목 | 값 |
|---|---|
| 인증 | O |
| 권한 | BOSS |
응답 형식:
{
"store": {
"id": 1,
"name": "투데이브레드 데모 강남점",
"phone": "02-7000-3001",
"description": "강남역 근처에서 소금빵과 식사용 빵을 판매하는 프론트 연동용 데모 매장입니다.",
"addressLine1": "서울특별시 강남구 테헤란로 123",
"addressLine2": "1층",
"latitude": 37.5826000,
"longitude": 127.0106000,
"businessHours": [
{
"dayOfWeek": 1,
"isClosed": false,
"startTime": "07:00:00",
"endTime": "23:00:00",
"lastOrderTime": "22:30:00"
}
]
},
"images": [
{
"id": 1,
"imageUrl": "/images/store_1_abc123.jpg",
"displayOrder": 0
},
{
"id": 2,
"imageUrl": "/images/store_1_def456.jpg",
"displayOrder": 1
}
]
}에러 응답: STORE_001, STORE_004
| 항목 | 값 |
|---|---|
| 인증 | O |
| 권한 | BOSS |
| Content-Type | multipart/form-data |
요청 파트:
request(JSON):
{
"name": "투데이브레드 강남점",
"phone": "02-7000-3001",
"description": "강남역 근처 빵집입니다.",
"addressLine1": "서울특별시 강남구 테헤란로 123",
"addressLine2": "1층",
"latitude": 37.5826000,
"longitude": 127.0106000,
"businessHours": [
{
"dayOfWeek": 1,
"isClosed": false,
"startTime": "07:00:00",
"endTime": "23:00:00",
"lastOrderTime": "22:30:00"
},
{
"dayOfWeek": 2,
"isClosed": false,
"startTime": "07:00:00",
"endTime": "23:00:00",
"lastOrderTime": "22:30:00"
},
{
"dayOfWeek": 3,
"isClosed": false,
"startTime": "07:00:00",
"endTime": "23:00:00",
"lastOrderTime": "22:30:00"
},
{
"dayOfWeek": 4,
"isClosed": false,
"startTime": "07:00:00",
"endTime": "23:00:00",
"lastOrderTime": "22:30:00"
},
{
"dayOfWeek": 5,
"isClosed": false,
"startTime": "07:00:00",
"endTime": "23:00:00",
"lastOrderTime": "22:30:00"
},
{
"dayOfWeek": 6,
"isClosed": false,
"startTime": "07:00:00",
"endTime": "23:00:00",
"lastOrderTime": "22:30:00"
},
{
"dayOfWeek": 7,
"isClosed": true,
"startTime": null,
"endTime": null,
"lastOrderTime": null
}
]
}images(파일 목록, 1~5장): 가게 이미지
영업시간은 7개 요일을 모두 전달해야 합니다. 영업일(
isClosed=false)은startTime,endTime,lastOrderTime이 모두 필수이고, 휴무일(isClosed=true)은 세 시간값이 모두null이어야 합니다. 자정 넘김 영업은 허용하지만startTime == endTime은 유효하지 않습니다.
응답 형식:
{
"store": {
"id": 1,
"name": "투데이브레드 강남점",
"phone": "02-7000-3001",
"description": "강남역 근처 빵집입니다.",
"addressLine1": "서울특별시 강남구 테헤란로 123",
"addressLine2": "1층",
"latitude": 37.5826000,
"longitude": 127.0106000,
"businessHours": [
{
"dayOfWeek": 1,
"isClosed": false,
"startTime": "07:00:00",
"endTime": "23:00:00",
"lastOrderTime": "22:30:00"
}
]
},
"images": [
{
"id": 1,
"imageUrl": "/images/store_1_abc123.jpg",
"displayOrder": 0
}
]
}에러 응답: STORE_001, STORE_002, STORE_003, STORE_005, STORE_006, COMMON_001, COMMON_005, COMMON_006, COMMON_007, STORE_IMAGE_002
| 항목 | 값 |
|---|---|
| 인증 | O |
| 권한 | BOSS |
요청 바디:
{
"name": "투데이브레드 강남점",
"phone": "02-7000-3001",
"description": "리뉴얼 오픈! 강남역 근처 빵집입니다.",
"addressLine1": "서울특별시 강남구 테헤란로 123",
"addressLine2": "1층",
"latitude": 37.5826000,
"longitude": 127.0106000,
"businessHours": [
{
"dayOfWeek": 1,
"isClosed": false,
"startTime": "08:00:00",
"endTime": "22:00:00",
"lastOrderTime": "21:30:00"
}
]
}영업시간 수정도 등록과 동일한 validation 정책을 사용합니다. 일반 영업은
lastOrderTime이 시작~종료 범위 안에 있어야 하고, 자정 넘김 영업은lastOrderTime >= startTime또는lastOrderTime <= endTime이면 유효합니다.
응답 형식:
{
"id": 1,
"name": "투데이브레드 강남점",
"phone": "02-7000-3001",
"description": "리뉴얼 오픈! 강남역 근처 빵집입니다.",
"addressLine1": "서울특별시 강남구 테헤란로 123",
"addressLine2": "1층",
"latitude": 37.5826000,
"longitude": 127.0106000,
"businessHours": [
{
"dayOfWeek": 1,
"isClosed": false,
"startTime": "08:00:00",
"endTime": "22:00:00",
"lastOrderTime": "21:30:00"
}
]
}에러 응답: STORE_001, STORE_003, STORE_004, STORE_005, STORE_006, COMMON_001
| 항목 | 값 |
|---|---|
| 인증 | O |
| 권한 | BOSS |
| Content-Type | multipart/form-data |
요청 파트: images (파일 목록, 1~5장)
응답 형식:
[
{
"id": 10,
"imageUrl": "/images/store_1_abc123.jpg",
"displayOrder": 0
},
{
"id": 11,
"imageUrl": "/images/store_1_def456.jpg",
"displayOrder": 1
}
]에러 응답: STORE_001, STORE_004, STORE_IMAGE_002, COMMON_001, COMMON_005, COMMON_006, COMMON_007
| 항목 | 값 |
|---|---|
| 인증 | O |
| 권한 | BOSS |
쿼리 파라미터:
| 파라미터 | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
page |
int | X | 0 | 페이지 번호 (0부터) |
size |
int | X | 20 | 페이지 크기 (최대 100) |
응답 형식 (Spring Page):
{
"content": [
{
"orderId": 42,
"orderNumber": "W6X7",
"totalAmount": 7500,
"createdAt": "2026-04-15T18:30:00",
"items": [
{
"orderItemId": 2000,
"breadName": "시그니처 소금빵",
"breadPrice": 2500,
"quantity": 3,
"breadImageUrl": null
}
]
}
],
"pageable": {
"pageNumber": 0,
"pageSize": 20
},
"totalElements": 2,
"totalPages": 1,
"last": true,
"first": true,
"empty": false
}사장님 주문 목록의
breadImageUrl은 항상null입니다. 사장님 화면에서는 빵 이미지가 불필요하므로 의도적으로 생략합니다.
에러 응답: STORE_001, STORE_004
| 항목 | 값 |
|---|---|
| 인증 | O |
| 권한 | BOSS |
경로 변수: orderId (주문 ID)
응답 형식: 응답 바디 없음 (HTTP 200)
에러 응답: STORE_001, STORE_004, ORDER_001, ORDER_002, ORDER_003
| 항목 | 값 |
|---|---|
| 인증 | O |
| 권한 | BOSS |
쿼리 파라미터: date (ISO DATE, 예: 2026-04-15)
응답 형식:
{
"totalSales": 18900,
"totalQuantity": 6,
"items": [
{
"breadId": 1,
"breadName": "시그니처 소금빵",
"breadPrice": 2500,
"totalQuantity": 4,
"totalSales": 10000
},
{
"breadId": 2,
"breadName": "바질 치아바타",
"breadPrice": 3900,
"totalQuantity": 2,
"totalSales": 7800
}
]
}삭제된 메뉴의
breadId는null로 표시됩니다.
에러 응답: STORE_001, STORE_004, COMMON_001
| 항목 | 값 |
|---|---|
| 인증 | O |
| 권한 | BOSS |
쿼리 파라미터:
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
year |
int | O | 연도 (2000 이상) |
month |
int | O | 월 (1 ~ 12) |
응답 형식:
{
"totalSales": 76000,
"totalQuantity": 28,
"dailySales": [
{
"date": "2026-03-02",
"totalSales": 7800
},
{
"date": "2026-03-04",
"totalSales": 9600
}
],
"items": [
{
"breadId": 1,
"breadName": "시그니처 소금빵",
"breadPrice": 2500,
"totalQuantity": 10,
"totalSales": 25000
},
{
"breadId": 2,
"breadName": "바질 치아바타",
"breadPrice": 3900,
"totalQuantity": 5,
"totalSales": 19500
}
]
}에러 응답: STORE_001, STORE_004, COMMON_001
| 항목 | 값 |
|---|---|
| 인증 | O |
요청 바디:
{
"keyword": "크루아상"
}응답 형식:
{
"success": true
}에러 응답: KEYWORD_001, KEYWORD_002, KEYWORD_003, COMMON_001
| 항목 | 값 |
|---|---|
| 인증 | O |
응답 형식:
[
{
"userKeywordId": 1,
"displayText": "크루아상"
},
{
"userKeywordId": 2,
"displayText": "소금빵"
}
]| 항목 | 값 |
|---|---|
| 인증 | O |
경로 변수: userKeywordId (사용자-키워드 관계 ID)
응답 형식:
{
"success": true
}에러 응답: KEYWORD_004, KEYWORD_005
| 항목 | 값 |
|---|---|
| 인증 | O |
응답 형식:
{
"interestArea": {
"id": 1,
"name": "한성대학교",
"address": "서울특별시 성북구 삼선교로16길 116",
"latitude": 37.5826000,
"longitude": 127.0106000,
"radiusKm": 3.0
}
}
name은 화면에 표시할 관심지역 이름이고,address는 도로명 주소입니다. 관심지역이 없으면interestArea는null입니다.
| 항목 | 값 |
|---|---|
| 인증 | O |
요청 바디:
{
"name": "한성대학교",
"address": "서울특별시 성북구 삼선교로16길 116",
"latitude": 37.5826000,
"longitude": 127.0106000
}응답 형식:
{
"id": 1,
"name": "한성대학교",
"address": "서울특별시 성북구 삼선교로16길 116",
"latitude": 37.5826000,
"longitude": 127.0106000,
"radiusKm": 3.0
}에러 응답: INTEREST_AREA_001, COMMON_001
| 항목 | 값 |
|---|---|
| 인증 | O |
요청 바디: POST /api/interest-area와 동일
응답 형식: POST /api/interest-area와 동일
에러 응답: INTEREST_AREA_002, COMMON_001
| 항목 | 값 |
|---|---|
| 인증 | O |
응답 형식:
{
"success": true,
"keywordNotificationDisabled": true
}에러 응답: INTEREST_AREA_002, COMMON_001
| 항목 | 값 |
|---|---|
| 인증 | O |
요청 바디:
{
"storeId": 1
}응답 형식:
{
"added": true
}
added: true→ 단골 추가됨,added: false→ 단골 해제됨
에러 응답: STORE_004, FAVOURITE_STORE_001, COMMON_001
| 항목 | 값 |
|---|---|
| 인증 | O |
응답 형식:
[
{
"storeId": 1,
"name": "투데이브레드 데모 강남점",
"address": "서울특별시 강남구 테헤란로 123 1층",
"imageUrl": "/images/store_1_abc123.jpg",
"isSelling": true
}
]| 항목 | 값 |
|---|---|
| 인증 | O |
응답 형식:
{
"keywords": [
{
"userKeywordId": 1,
"displayText": "크루아상"
},
{
"userKeywordId": 2,
"displayText": "소금빵"
}
],
"favouriteStores": [
{
"storeId": 1,
"name": "투데이브레드 데모 강남점",
"address": "서울특별시 강남구 테헤란로 123 1층",
"imageUrl": "/images/store_1_abc123.jpg",
"isSelling": true
}
]
}| 항목 | 값 |
|---|---|
| 인증 | O |
| 응답 코드 | 201 Created |
요청 바디:
{
"breadId": 1,
"quantity": 2
}응답 형식: 응답 바디 없음 (HTTP 201)
에러 응답: BREAD_001, BREAD_003, CART_001, COMMON_001
| 항목 | 값 |
|---|---|
| 인증 | O |
응답 형식:
{
"storeName": "투데이브레드 데모 강남점",
"lastOrderTime": "22:30:00",
"items": [
{
"cartItemId": 1,
"breadId": 1,
"breadName": "시그니처 소금빵",
"description": "겉은 바삭하고 속은 촉촉한 대표 메뉴입니다.",
"quantity": 2,
"imageUrl": "/images/bread_1_abc123.jpg",
"salePrice": 2500
}
]
}장바구니에 담긴 빵이 Soft Delete 처리되면 장바구니 조회 시 해당 항목은 자동 제거됩니다. 제거 후 유효 항목이 없으면
storeName,lastOrderTime은null,items는 빈 배열로 반환됩니다.
에러 응답: STORE_004
| 항목 | 값 |
|---|---|
| 인증 | O |
경로 변수: cartItemId (장바구니 항목 ID)
요청 바디:
{
"quantity": 3
}응답 형식: 응답 바디 없음 (HTTP 200)
에러 응답: BREAD_001, BREAD_003, CART_002, CART_003, COMMON_001
| 항목 | 값 |
|---|---|
| 인증 | O |
| 응답 코드 | 204 No Content |
경로 변수: cartItemId (장바구니 항목 ID)
응답 형식: 응답 바디 없음 (HTTP 204)
에러 응답: CART_002, CART_003
| 항목 | 값 |
|---|---|
| 인증 | O |
| 응답 코드 | 204 No Content |
응답 형식: 응답 바디 없음 (HTTP 204)
주문 생성 API(
POST /api/orders/cart,POST /api/orders/direct)는Idempotency-Key헤더가 필수입니다. 네트워크 오류 등으로 응답을 받지 못했을 때 같은 key로 재요청하면 동일한 주문 결과를 반환합니다. 새로운 주문을 생성하려면 반드시 새로운 key를 사용하세요. (UUID v4 권장)
| 항목 | 값 |
|---|---|
| 인증 | O |
| 필수 헤더 | Idempotency-Key |
응답 형식:
{
"orderId": 42,
"storeName": "투데이브레드 데모 강남점",
"status": "PENDING",
"totalAmount": 7500,
"orderNumber": "W6X7",
"createdAt": "2026-04-15T18:30:00",
"items": [
{
"orderItemId": 2000,
"breadName": "시그니처 소금빵",
"breadPrice": 2500,
"quantity": 3,
"breadImageUrl": "/images/bread_1_abc123.jpg"
}
]
}에러 응답: CART_003, BREAD_001, BREAD_003, COMMON_001, COMMON_008, ORDER_004
| 항목 | 값 |
|---|---|
| 인증 | O |
| 필수 헤더 | Idempotency-Key |
요청 바디:
{
"breadId": 1,
"quantity": 2
}응답 형식:
{
"orderId": 43,
"storeName": "투데이브레드 데모 강남점",
"status": "PENDING",
"totalAmount": 5000,
"orderNumber": "A1B2",
"createdAt": "2026-04-15T19:00:00",
"items": [
{
"orderItemId": 2001,
"breadName": "시그니처 소금빵",
"breadPrice": 2500,
"quantity": 2,
"breadImageUrl": "/images/bread_1_abc123.jpg"
}
]
}에러 응답: BREAD_001, BREAD_003, COMMON_001, COMMON_008, ORDER_004
| 항목 | 값 |
|---|---|
| 인증 | O |
경로 변수: orderId (주문 ID)
응답 형식: 응답 바디 없음 (HTTP 200)
에러 응답: ORDER_001, ORDER_002, ORDER_003, PAYMENT_007
| 항목 | 값 |
|---|---|
| 인증 | O |
쿼리 파라미터:
| 파라미터 | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
page |
int | X | 0 | 페이지 번호 (0부터) |
size |
int | X | 20 | 페이지 크기 (최대 100) |
응답 형식 (Spring Page):
{
"content": [
{
"orderId": 42,
"storeName": "투데이브레드 데모 강남점",
"status": "CONFIRMED",
"totalAmount": 7500,
"orderNumber": "W6X7",
"createdAt": "2026-04-15T18:30:00"
}
],
"pageable": {
"pageNumber": 0,
"pageSize": 20
},
"totalElements": 15,
"totalPages": 1,
"last": true,
"first": true,
"empty": false
}주문 상태:
PENDING,CONFIRMED,CANCEL_PENDING,CANCELLED,PICKED_UP주문 항목의orderItemId는 리뷰 작성(POST /api/review) 시 사용합니다.
| 항목 | 값 |
|---|---|
| 인증 | O |
경로 변수: orderId (주문 ID)
응답 형식:
{
"orderId": 42,
"storeName": "투데이브레드 데모 강남점",
"status": "CONFIRMED",
"totalAmount": 7500,
"orderNumber": "W6X7",
"createdAt": "2026-04-15T18:30:00",
"items": [
{
"orderItemId": 2000,
"breadName": "시그니처 소금빵",
"breadPrice": 2500,
"quantity": 3,
"breadImageUrl": "/images/bread_1_abc123.jpg"
}
]
}에러 응답: ORDER_001, ORDER_002
결제 승인 확정 API(
POST /api/payments/confirm)는Idempotency-Key헤더가 필수입니다. 같은 key로 재요청하면 토스 Confirm API를 중복 호출하지 않고 기존 결제 결과를 반환합니다. 결제 실패 후 재시도할 때는 새로운 key를 사용하세요. (UUID v4 권장)
| 항목 | 값 |
|---|---|
| 인증 | O |
| 필수 헤더 | Idempotency-Key |
요청 바디:
{
"paymentKey": "tgen_20250101010101ABCDE",
"orderId": 42,
"amount": 7500
}응답 형식:
{
"paymentId": 1,
"orderId": 42,
"amount": 7500,
"status": "APPROVED",
"paidAt": "2026-04-15T18:31:00",
"method": "카드"
}결제 상태:
PENDING,APPROVED,FAILED,CANCELLED
에러 응답: PAYMENT_001, PAYMENT_003, PAYMENT_004, PAYMENT_008, ORDER_001, ORDER_002, COMMON_001
토스 4xx 오류는 토스에서 내려준 원본 에러 코드와 메시지가 그대로 반환될 수 있습니다.
| 항목 | 값 |
|---|---|
| 인증 | X |
응답 형식:
{
"clientKey": "test_ck_..."
}프론트엔드에서 토스 결제 위젯을 초기화할 때 사용합니다. Client Key는 공개 키이므로 인증 없이 접근 가능합니다.
| 항목 | 값 |
|---|---|
| 인증 | O |
| 권한 | USER |
| Content-Type | multipart/form-data |
| 응답 코드 | 201 Created |
요청 파트:
request(JSON):
{
"orderItemId": 2000,
"rating": 4,
"content": "정말 맛있는 빵이었습니다! 추천합니다."
}images(파일 목록, 선택): 리뷰 이미지 (최대 2장)
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
orderItemId |
Long | O | 주문 항목 ID |
rating |
int | O | 평점 (1~5) |
content |
String | O | 리뷰 내용 (10~500자) |
응답 형식:
{
"reviewId": 1,
"orderItemId": 2000,
"rating": 4,
"content": "정말 맛있는 빵이었습니다! 추천합니다.",
"imageUrls": ["/images/review_1_abc123.jpg"],
"createdAt": "2026-04-15T18:30:00"
}수령 완료(PICKED_UP) 상태의 주문 항목에 대해서만 리뷰 작성이 가능합니다. 동일 주문 항목에 대해 중복 리뷰는 허용되지 않습니다.
에러 응답: ORDER_001, REVIEW_001, REVIEW_002, REVIEW_003, REVIEW_005, COMMON_001, COMMON_005, COMMON_006, COMMON_007
| 항목 | 값 |
|---|---|
| 인증 | O |
| 권한 | USER |
경로 변수: storeId (가게 ID)
쿼리 파라미터:
| 파라미터 | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
sort |
String | X | LATEST | 정렬: LATEST, RATING_HIGH, RATING_LOW |
page |
int | X | 0 | 페이지 번호 (0부터) |
size |
int | X | 20 | 페이지 크기 (최대 100) |
응답 형식 (Spring Page):
{
"content": [
{
"reviewId": 1,
"nickname": "빵순이",
"rating": 4,
"content": "정말 맛있는 빵이었습니다! 추천합니다.",
"breadName": "시그니처 소금빵",
"breadImageUrl": "/images/bread_1_abc123.jpg",
"imageUrls": ["/images/review_1_abc123.jpg"],
"createdAt": "2026-04-15T18:30:00"
}
],
"pageable": { "pageNumber": 0, "pageSize": 20 },
"totalElements": 5,
"totalPages": 1,
"last": true,
"first": true,
"empty": false
}
breadName은 주문 시점의 빵 이름 스냅샷입니다. 빵 이름이 변경되어도 리뷰에는 구매 당시 이름이 표시됩니다. 현재 컨트롤러 클래스에@PreAuthorize("hasRole('USER')")가 적용되어 있어 BOSS 토큰이나 비로그인 요청으로는 이 API를 조회할 수 없습니다.
에러 응답: STORE_004, COMMON_001
| 항목 | 값 |
|---|---|
| 인증 | O |
| 권한 | USER |
쿼리 파라미터:
| 파라미터 | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
sort |
String | X | LATEST | 정렬: LATEST, OLDEST |
page |
int | X | 0 | 페이지 번호 (0부터) |
size |
int | X | 20 | 페이지 크기 (최대 100) |
응답 형식 (Spring Page):
{
"content": [
{
"reviewId": 1,
"breadName": "시그니처 소금빵",
"breadImageUrl": "/images/bread_1_abc123.jpg",
"storeName": "투데이브레드 데모 강남점",
"storeId": 1,
"rating": 4,
"content": "정말 맛있는 빵이었습니다! 추천합니다.",
"imageUrls": ["/images/review_1_abc123.jpg"],
"createdAt": "2026-04-15T18:30:00"
}
],
"pageable": { "pageNumber": 0, "pageSize": 20 },
"totalElements": 3,
"totalPages": 1,
"last": true,
"first": true,
"empty": false
}에러 응답: COMMON_001
| 항목 | 값 |
|---|---|
| 인증 | O |
| 권한 | BOSS |
쿼리 파라미터:
| 파라미터 | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
sort |
String | X | LATEST | 정렬: LATEST, OLDEST, RATING_HIGH, RATING_LOW |
filter |
String | X | ALL | 필터: ALL, WITH_IMAGE, TEXT_ONLY |
page |
int | X | 0 | 페이지 번호 (0부터) |
size |
int | X | 20 | 페이지 크기 (최대 100) |
응답 형식 (Spring Page):
{
"content": [
{
"reviewId": 1,
"nickname": "빵순이",
"rating": 4,
"content": "정말 맛있는 빵이었습니다! 추천합니다.",
"breadName": "시그니처 소금빵",
"breadImageUrl": "/images/bread_1_abc123.jpg",
"imageUrls": ["/images/review_1_abc123.jpg"],
"createdAt": "2026-04-15T18:30:00",
"purchaseCount": 3
}
],
"pageable": { "pageNumber": 0, "pageSize": 20 },
"totalElements": 10,
"totalPages": 1,
"last": true,
"first": true,
"empty": false
}
purchaseCount는 해당 리뷰 작성자의 이 가게 총 구매 횟수(PICKED_UP 기준)입니다.
에러 응답: STORE_001, STORE_004, COMMON_001
| 항목 | 값 |
|---|---|
| 인증 | X |
응답 형식:
"UP"
- JWT (HMAC-SHA256) 기반 stateless 인증
- 비밀번호 암호화: Argon2
- 역할 계층:
BOSS > USER - 인증 불필요 경로 (permitAll):
/api/user/register— 회원가입/api/user/login— 로그인/api/user/exist/**— 이메일/닉네임/전화번호 중복확인/api/auth/reissue— 토큰 재발급/api/user/find-email— 이메일 찾기/api/user/verify-identity— 본인 확인/api/user/reset-password— 비밀번호 재설정/api/payments/client-key— 토스 Client Key 조회/api/system/health— 헬스체크/swagger-ui/**— Swagger UI/v3/api-docs/**— OpenAPI 스펙/images/**— 로컬/test 프로필 이미지 정적 파일
- 그 외 모든 경로는
Authorization: Bearer {accessToken}필요 - 사장님 전용 API (
/api/boss/**)는@PreAuthorize("hasRole('BOSS')")적용
모든 에러는 아래 형식으로 반환됩니다:
{
"code": "ERROR_CODE",
"message": "에러 메시지"
}| 코드 | HTTP | 메시지 |
|---|---|---|
COMMON_001 |
400 | 요청값 검증에 실패했습니다. |
COMMON_002 |
405 | 허용되지 않은 HTTP 메서드입니다. |
COMMON_003 |
500 | 서버 내부 오류입니다. |
COMMON_004 |
403 | 접근 권한이 없습니다. |
COMMON_005 |
400 | 파일 크기는 5MB를 초과할 수 없습니다. |
COMMON_006 |
400 | 허용되지 않는 파일 형식입니다. (jpeg, png, gif, webp만 가능) |
COMMON_007 |
500 | 파일 저장에 실패했습니다. |
COMMON_008 |
409 | 중복된 데이터가 존재합니다. |
| 코드 | HTTP | 메시지 |
|---|---|---|
USER_001 |
409 | 이미 가입한 이메일입니다. |
USER_002 |
409 | 이미 가입한 전화번호입니다. |
USER_003 |
409 | 이미 사용중인 닉네임입니다. |
USER_004 |
404 | 사용자를 찾을 수 없습니다. |
USER_005 |
404 | 가입 정보를 찾을 수 없습니다. |
USER_006 |
409 | 이미 사장님 등록이 완료된 상태입니다. |
USER_007 |
400 | 사업자 번호 형식이 맞지 않습니다. |
USER_008 |
400 | 유효하지 않은 비밀번호 재설정 토큰입니다. |
USER_009 |
400 | 사업자 등록정보가 일치하지 않습니다. |
USER_010 |
409 | 영업 중인 사업자만 등록할 수 있습니다. |
USER_011 |
409 | 이미 등록된 사업자번호입니다. |
USER_012 |
503 | 사업자 등록정보 확인이 지연되고 있습니다. 잠시 후 다시 시도해주세요. |
| 코드 | HTTP | 메시지 |
|---|---|---|
KEYWORD_001 |
409 | 이미 등록된 키워드입니다. |
KEYWORD_002 |
400 | 키워드는 최대 5개까지 등록할 수 있습니다. |
KEYWORD_003 |
400 | 키워드는 최대 10자까지 입력할 수 있습니다. |
KEYWORD_004 |
404 | 키워드를 찾을 수 없습니다. |
KEYWORD_005 |
403 | 해당 키워드에 대한 권한이 없습니다. |
| 코드 | HTTP | 메시지 |
|---|---|---|
INTEREST_AREA_001 |
409 | 이미 관심지역이 등록되어 있습니다. |
INTEREST_AREA_002 |
404 | 관심지역을 찾을 수 없습니다. |
INTEREST_AREA_003 |
400 | 키워드 알림을 위해 관심지역 설정이 필요합니다. |
| 코드 | HTTP | 메시지 |
|---|---|---|
STORE_001 |
403 | 사장님 등록 후 이용 가능한 기능입니다. |
STORE_002 |
409 | 이미 등록된 가게가 있습니다. |
STORE_003 |
409 | 가게 전화번호가 중복이 됩니다. |
STORE_004 |
404 | 가게를 찾을 수 없습니다. |
STORE_005 |
400 | 영업시간 데이터가 올바르지 않습니다. |
STORE_006 |
400 | 요일 데이터가 중복됩니다. |
| 코드 | HTTP | 메시지 |
|---|---|---|
STORE_IMAGE_001 |
404 | 이미지를 찾을 수 없습니다. |
STORE_IMAGE_002 |
400 | 이미지는 최대 5장까지 등록할 수 있습니다. |
| 코드 | HTTP | 메시지 |
|---|---|---|
FAVOURITE_STORE_001 |
400 | 단골 가게는 최대 5개까지 등록할 수 있습니다. |
| 코드 | HTTP | 메시지 |
|---|---|---|
BREAD_001 |
404 | 상품을 찾을 수 없습니다. |
BREAD_002 |
403 | 상품에 접근할 권한이 없습니다. |
BREAD_003 |
409 | 해당 상품의 재고가 부족합니다. |
BREAD_004 |
400 | 가격은 0원 이상이여야 합니다. |
빵 이미지 관련 에러(파일 형식, 크기, 저장 실패)는 공통 에러 코드
COMMON_005,COMMON_006,COMMON_007을 사용합니다.
| 코드 | HTTP | 메시지 |
|---|---|---|
AUTH_001 |
401 | Access 토큰이 만료되었습니다. |
AUTH_002 |
401 | 유효하지 않은 Access 토큰입니다. |
AUTH_003 |
401 | 유효하지 않은 Refresh 토큰입니다. |
| 코드 | HTTP | 메시지 |
|---|---|---|
CART_001 |
409 | 장바구니에는 하나의 매장 빵만 담을 수 있습니다. |
CART_002 |
404 | 장바구니 항목을 찾을 수 없습니다. |
CART_003 |
400 | 장바구니가 비어 있습니다. |
| 코드 | HTTP | 메시지 |
|---|---|---|
ORDER_001 |
404 | 주문을 찾을 수 없습니다. |
ORDER_002 |
403 | 주문에 접근할 권한이 없습니다. |
ORDER_003 |
409 | 변경할 수 없는 주문 상태입니다. |
ORDER_004 |
500 | 주문 번호 생성에 실패했습니다. |
| 코드 | HTTP | 메시지 |
|---|---|---|
PAYMENT_001 |
400 | 결제 금액이 주문 금액과 일치하지 않습니다. |
PAYMENT_003 |
409 | 결제할 수 없는 주문 상태입니다. |
PAYMENT_004 |
502 | 결제 처리 중 오류가 발생했습니다. 잠시 후 다시 시도해주세요. |
PAYMENT_007 |
502 | 결제 취소 처리 중 오류가 발생했습니다. |
PAYMENT_008 |
400 | Idempotency-Key 헤더가 필요합니다. |
| 코드 | HTTP | 메시지 |
|---|---|---|
REVIEW_001 |
400 | 구매 이력이 없어 리뷰를 작성할 수 없습니다. |
REVIEW_002 |
409 | 이미 해당 주문 항목에 대한 리뷰를 작성했습니다. |
REVIEW_003 |
400 | 해당 상품이 삭제되어 리뷰를 작성할 수 없습니다. |
REVIEW_005 |
400 | 리뷰 이미지는 최대 2장까지 첨부할 수 있습니다. |