1. API 목록

2. POST: 회원가입 이메일 인증

이메일 인증 시도는 계정당 하루에 5회만 가능합니다. 5회 이상 인증 코드 요청 시 24시간 뒤 재요청 가능합니다.
HTTP Request
POST /sign-up/email-auth HTTP/1.1
Content-Type: application/json;charset=UTF-8
Content-Length: 37
Host: localhost:8080

{
  "email" : "example@pusan.ac.kr"
}
HTTP Response
HTTP/1.1 201 Created
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Table 1. Request Fields
Path Type Description

email

String

가입 이메일

2.1. ⚠️ 실패 케이스

❌ Case 1: 인증 이메일이 부산대 이메일이 아님
POST /sign-up/email-auth HTTP/1.1
Content-Type: application/json;charset=UTF-8
Content-Length: 32
Host: localhost:8080

{
  "email" : "test@gmail.com"
}
HTTP/1.1 400 Bad Request
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: application/json;charset=UTF-8
Content-Length: 66

{
  "message" : "부산대 이메일만 가입 가능합니다."
}
Path Type Description

email

String

잘못된 도메인의 이메일 (부산대 메일이 아님)

3. PATCH: 회원가입 이메일 인증코드 확인

HTTP Request
PATCH /sign-up/email-auth HTTP/1.1
Content-Type: application/json;charset=UTF-8
Content-Length: 67
Host: localhost:8080

{
  "email" : "example@pusan.ac.kr",
  "authCode" : "exampleCode"
}
HTTP Response
HTTP/1.1 204 No Content
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Table 2. Request Fields
Path Type Description

email

String

가입 이메일

authCode

String

인증 코드

3.1. ⚠️ 실패 케이스

❌ Case 1: 인증번호 불일치
PATCH /sign-up/email-auth HTTP/1.1
Content-Type: application/json;charset=UTF-8
Content-Length: 66
Host: localhost:8080

{
  "email" : "example@pusan.ac.kr",
  "authCode" : "wrong_code"
}
HTTP/1.1 400 Bad Request
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: application/json;charset=UTF-8
Content-Length: 73

{
  "message" : "이메일 인증 코드가 일치하지 않습니다."
}
Path Type Description

email

String

가입 이메일

authCode

String

틀린 인증 코드

❌ Case 2: 만료된 인증코드
PATCH /sign-up/email-auth HTTP/1.1
Content-Type: application/json;charset=UTF-8
Content-Length: 68
Host: localhost:8080

{
  "email" : "example@pusan.ac.kr",
  "authCode" : "expired_code"
}
HTTP/1.1 400 Bad Request
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: application/json;charset=UTF-8
Content-Length: 83

{
  "message" : "이메일 인증 코드 만료 시간이 초과되었습니다."
}
Path Type Description

email

String

가입 이메일

authCode

String

만료된 인증 코드

4. POST: 회원가입

HTTP Request
POST /sign-up HTTP/1.1
Content-Type: application/json;charset=UTF-8
Content-Length: 123
Host: localhost:8080

{
  "name" : "테스트회원",
  "studentId" : "202612345",
  "email" : "example@pusan.ac.kr",
  "password" : "qwer123!"
}
HTTP Response
HTTP/1.1 201 Created
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Table 3. Request Fields
Path Type Description

name

String

회원 이름

studentId

String

회원의 학번

email

String

회원의 이메일

password

String

회원의 비밀번호

5. POST: 로그인

HTTP Request
POST /sign-in HTTP/1.1
Content-Type: application/json;charset=UTF-8
Content-Length: 64
Host: localhost:8080

{
  "email" : "example@pusan.ac.kr",
  "password" : "qwer123!"
}
HTTP Response
HTTP/1.1 200 OK
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: application/json;charset=UTF-8
Content-Length: 109

{
  "memberId" : 1,
  "name" : "테스트회원",
  "token" : "exampleToken",
  "types" : [ "ROLE_회원" ]
}
Table 4. Request Fields
Path Type Description

email

String

로그인 이메일

password

String

비밀번호 (영문+숫자+특수문자 조합)

Table 5. Response Body’s Fields
Path Type Description

memberId

Number

회원 고유 식별자

name

String

회원 이름

token

String

JWT 액세스 토큰

types

Array

회원 권한 목록(회원, 관리자)

6. POST: 비밀번호 변경 이메일 인증

이메일 인증 시도는 계정당 하루에 5회만 가능합니다. 5회 이상 인증 코드 요청 시 24시간 뒤 재요청 가능합니다.
HTTP Request
POST /sign-in/password-reset/email-auth HTTP/1.1
Content-Type: application/json;charset=UTF-8
Content-Length: 37
Host: localhost:8080

{
  "email" : "example@pusan.ac.kr"
}
HTTP Response
HTTP/1.1 201 Created
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Table 6. Request Fields
Path Type Description

email

String

가입 이메일

7. PATCH: 비밀번호 변경 이메일 인증코드 확인

HTTP Request
PATCH /sign-in/password-reset/email-auth HTTP/1.1
Content-Type: application/json;charset=UTF-8
Content-Length: 67
Host: localhost:8080

{
  "email" : "example@pusan.ac.kr",
  "authCode" : "exampleCode"
}
HTTP Response
HTTP/1.1 204 No Content
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Table 7. Request Fields
Path Type Description

email

String

가입 이메일

authCode

String

인증 코드

7.1. ⚠️ 실패 케이스

회원가입 이메일 인증 실패 케이스와 동일

8. PATCH: 비밀번호 변경

HTTP Request
PATCH /sign-in/password-reset HTTP/1.1
Content-Type: application/json;charset=UTF-8
Content-Length: 72
Host: localhost:8080

{
  "email" : "example@pusan.ac.kr",
  "newPassword" : "newPassword1!"
}
HTTP Response
HTTP/1.1 204 No Content
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Table 8. Request Fields
Path Type Description

email

String

가입 이메일

newPassword

String

새로운 비밀번호

9. GET : 가입 이메일 찾기

HTTP Request
GET /sign-in/1/email-find HTTP/1.1
Host: localhost:8080
HTTP Response
HTTP/1.1 200 OK
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: application/json;charset=UTF-8
Content-Length: 37

{
  "email" : "example@pusan.ac.kr"
}
Table 9. /sign-in/{studentId}/email-find
Parameter Description

studentId

가입 학번

Table 10. Response Body’s Fields
Path Type Description

email

String

가입된 이메일

10. POST: 로컬 환경 OAuth 리다이렉트 설정

로컬 개발 환경에서 구글 소셜 로그인 테스트 시 사전에 호출해야 합니다. 호출하지 않으면 기본값인 배포 사이트 URL로 리다이렉트됩니다.
HTTP Request
POST /oauth2/set-redirect HTTP/1.1
Host: localhost:8080
Content-Type: application/x-www-form-urlencoded
HTTP Response
HTTP/1.1 204 No Content
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Set-Cookie: redirect_type=local; Path=/; Max-Age=180; Expires=Sat, 28 Mar 2026 06:21:12 GMT; Secure; HttpOnly; SameSite=None

11. GET: Google OAuth 로그인 리다이렉트

Spring Security가 자동으로 처리하는 엔드포인트입니다. CSRF 방어용 state 파라미터를 자동으로 생성하고 세션에 저장합니다.
HTTP Request
GET /oauth2/authorization/google HTTP/1.1
Host: opus.pnu.app
HTTP Response
HTTP/1.1 302 Found
Location: https://accounts.google.com/o/oauth2/v2/auth
          ?scope=email+profile
          &response_type=code
          &client_id=YOUR_CLIENT_ID
          &redirect_uri=https://opus.pnu.app/login/oauth2/code/google
          &state=SPRING_SECURITY_GENERATED_STATE

12. GET: Google OAuth 콜백

구글 인증 완료 후 구글 서버가 자동으로 호출하는 엔드포인트입니다. 직접 호출할 필요 없으며 에러 처리 부분만 참고해주세요.
HTTP Request
GET /login/oauth2/code/google?code=4%2F0AX4X...&state=GENERATED_STATE HTTP/1.1
Host: opus.pnu.app
HTTP Response (성공)
HTTP/1.1 302 Found
Location: https://opus.pnu.app/oauth/callback?token=eyJhbGci...
HTTP Response (실패)
HTTP/1.1 302 Found
Location: https://opus.pnu.app/oauth/callback?error=URL인코딩된값

12.1. ⚠️ 실패 케이스

Details
디코딩된 error 값 원인

access_denied

사용자가 구글 로그인 취소

authorization_request_not_found

세션 만료 또는 뒤로가기 후 재시도

invalid_state_parameter

CSRF 의심

server_error

구글 서버 오류

GENERAL_MEMBER_CANNOT_USE_SOCIAL_LOGIN

일반 회원이 소셜 로그인 시도

error 파라미터는 URL 인코딩되어 있으므로 반드시 decodeURIComponent 처리가 필요합니다!

13. GET: 메인 통계 요약

HTTP Request
GET /statistics/summary HTTP/1.1
Host: localhost:8080
HTTP Response
HTTP/1.1 200 OK
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: application/json;charset=UTF-8
Content-Length: 71

{
  "totalProjects" : 42,
  "totalLikes" : 128,
  "totalContests" : 5
}
Table 11. Response Body’s Fields
Path Type Description

totalProjects

Number

등록된 프로젝트 수

totalLikes

Number

총 좋아요 수

totalContests

Number

진행된 대회 수

14. GET: 나의 프로젝트 조회

로그인한 사용자가 팀원으로 참여한 모든 대회의 프로젝트 목록을 조회합니다. 수상 정보를 포함하며, 페이지네이션 없이 전체 반환됩니다.
HTTP Request
GET /members/me/projects HTTP/1.1
Authorization: Bearer exampleToken
Host: localhost:8080
HTTP Response
HTTP/1.1 200 OK
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: application/json;charset=UTF-8
Content-Length: 487

[ {
  "contestId" : 1,
  "contestName" : "제6회창의융합해커톤대회",
  "teamId" : 10,
  "teamName" : "PNUops",
  "projectName" : "SW성과관리시스템",
  "trackName" : "AI/빅데이터",
  "awards" : [ {
    "awardName" : "대상",
    "awardColor" : "#FF0000"
  } ]
}, {
  "contestId" : 2,
  "contestName" : "제5회창의융합해커톤대회",
  "teamId" : 22,
  "teamName" : "해커톤의신",
  "projectName" : "PNUDataKing",
  "trackName" : null,
  "awards" : [ ]
} ]
Table 12. Response Body’s Fields
Path Type Description

[].contestId

Number

대회 ID

[].contestName

String

대회명

[].teamId

Number

팀 ID

[].teamName

String

팀명

[].projectName

String

프로젝트명

[].trackName

String

트랙(분과)명

[].awards

Array

수상 내역 리스트

[].awards[].awardName

String

수상명

[].awards[].awardColor

String

수상 색상

15. GET: 나의 투표 기록 조회

현재 투표 기간에 해당하는 대회에서 투표한 프로젝트 목록을 조회합니다. 투표 기간이 아닌 경우 빈 배열을 반환하며, 최신순으로 정렬됩니다.
HTTP Request
GET /members/me/votes HTTP/1.1
Authorization: Bearer exampleToken
Host: localhost:8080
HTTP Response
HTTP/1.1 200 OK
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: application/json;charset=UTF-8
Content-Length: 332

[ {
  "contestId" : 1,
  "contestName" : "제6회창의융합해커톤대회",
  "teamId" : 5,
  "teamName" : "Team Alpha",
  "projectName" : "알파 프로젝트"
}, {
  "contestId" : 1,
  "contestName" : "제6회창의융합해커톤대회",
  "teamId" : 12,
  "teamName" : "Team Beta",
  "projectName" : "베타 프로젝트"
} ]
Table 13. Response Body’s Fields
Path Type Description

[].contestId

Number

대회 ID

[].contestName

String

대회명

[].teamId

Number

팀 ID

[].teamName

String

팀명

[].projectName

String

프로젝트명

16. PATCH: 학번 수정

부산대 메일로 구글 소셜 로그인한 사용자에 한해 1회만 수정 가능합니다.
HTTP Request
PATCH /members/me/student-id HTTP/1.1
Content-Type: application/json;charset=UTF-8
Authorization: Bearer exampleToken
Content-Length: 31
Host: localhost:8080

{
  "studentId" : "202512345"
}
HTTP Response
HTTP/1.1 204 No Content
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Table 14. Request Fields
Path Type Description

studentId

String

변경할 학번

16.1. ⚠️ 실패 케이스

❌ Case 1: 학번 수정 불가 사용자 (소셜 회원 가입자만 1회의 학번 수정이 가능합니다.)
PATCH /members/me/student-id HTTP/1.1
Content-Type: application/json;charset=UTF-8
Authorization: Bearer exampleToken
Content-Length: 31
Host: localhost:8080

{
  "studentId" : "202512345"
}
HTTP/1.1 400 Bad Request
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: application/json;charset=UTF-8
Content-Length: 88

{
  "message" : "소셜 회원 가입자만 1회의 학번 수정이 가능합니다."
}

17. 프로필 이미지 관리

17.1. GET: 프로필 이미지 조회

HTTP Request
GET /members/me/images/profile HTTP/1.1
Authorization: Bearer member.access.token
Host: localhost:8080
HTTP Response
HTTP/1.1 200 OK
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: image/png;charset=UTF-8
Accept-Ranges: bytes
Content-Length: 18

test-image-content
Table 15. Request Headers
Name Description

Authorization

Bearer (회원).access.token

17.1.1. ⚠️ 실패 케이스

❌ Case 1: 등록되지 않은 프로필 이미지
GET /members/me/images/profile HTTP/1.1
Authorization: Bearer member.access.token
Host: localhost:8080
HTTP/1.1 404 Not Found
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: application/json;charset=UTF-8
Content-Length: 71

{
  "message" : "아이디와 일치하는 이미지가 없습니다"
}

17.2. PATCH: 프로필 이미지 변경

HTTP Request
PATCH /members/me/images/profile HTTP/1.1
Content-Type: multipart/form-data;charset=UTF-8; boundary=6o2knFse3p53ty9dmcQvWAIx1zInP11uCfbm
Authorization: Bearer member.access.token
Host: localhost:8080

--6o2knFse3p53ty9dmcQvWAIx1zInP11uCfbm
Content-Disposition: form-data; name=image; filename=profile.png
Content-Type: image/png

test-image-content
--6o2knFse3p53ty9dmcQvWAIx1zInP11uCfbm--
HTTP Response
HTTP/1.1 204 No Content
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Table 16. Request Headers
Name Description

Authorization

Bearer (회원).access.token

Table 17. Request Parts
Part Description

image

변경할 프로필 이미지 (모든 이미지 형식 지원)

17.3. DELETE: 프로필 이미지 삭제

HTTP Request
DELETE /members/me/images/profile HTTP/1.1
Authorization: Bearer member.access.token
Host: localhost:8080
HTTP Response
HTTP/1.1 204 No Content
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Table 18. Request Headers
Name Description

Authorization

Bearer (회원).access.token

18. DELETE: 회원 탈퇴

HTTP Request
DELETE /members/me HTTP/1.1
Authorization: Bearer member.access.token
Host: localhost:8080
HTTP Response
HTTP/1.1 204 No Content
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Table 19. Request Headers
Name Description

Authorization

Bearer (회원).access.token

19. DELETE: 관리자 강제 회원 탈퇴

관리자 권한(ROLE_관리자)이 필요합니다.
HTTP Request
DELETE /admin/members/1 HTTP/1.1
Authorization: Bearer admin.access.token
Host: localhost:8080
HTTP Response
HTTP/1.1 204 No Content
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Table 20. Request Headers
Name Description

Authorization

Bearer (관리자).access.token

Table 21. /admin/members/{memberId}
Parameter Description

memberId

탈퇴시킬 회원 ID

19.1. ⚠️ 실패 케이스

❌ Case 1: 존재하지 않는 회원 ID
DELETE /admin/members/999 HTTP/1.1
Authorization: Bearer admin.access.token
Host: localhost:8080
HTTP/1.1 404 Not Found
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: application/json;charset=UTF-8
Content-Length: 54

{
  "message" : "회원을 찾을 수 없습니다."
}
Table 22. /admin/members/{memberId}
Parameter Description

memberId

존재하지 않는 회원 ID

20. GET: 계정 정보 조회

HTTP Request
GET /members/me HTTP/1.1
Authorization: Bearer exampleToken
Host: localhost:8080
HTTP Response
HTTP/1.1 200 OK
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: application/json;charset=UTF-8
Content-Length: 117

{
  "name" : "테스트회원",
  "email" : "example@pusan.ac.kr",
  "githubUrl" : null,
  "isProfilePublic" : true
}
Table 23. Response Body’s Fields
Path Type Description

name

String

이름

email

String

이메일

githubUrl

String

GitHub 링크

isProfilePublic

Boolean

프로필 공개 여부

21. PATCH: GitHub 링크 수정

HTTP Request
PATCH /members/me/github-url HTTP/1.1
Content-Type: application/json;charset=UTF-8
Authorization: Bearer exampleToken
Content-Length: 51
Host: localhost:8080

{
  "githubUrl" : "https://github.com/hongjiyeon"
}
HTTP Response
HTTP/1.1 204 No Content
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Table 24. Request Fields
Path Type Description

githubUrl

String

GitHub URL

22. PATCH: 프로필 공개 여부 변경

HTTP Request
PATCH /members/me/profile-visibility HTTP/1.1
Content-Type: application/json;charset=UTF-8
Authorization: Bearer exampleToken
Content-Length: 30
Host: localhost:8080

{
  "isProfilePublic" : true
}
HTTP Response
HTTP/1.1 204 No Content
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Table 25. Request Fields
Path Type Description

isProfilePublic

Boolean

프로필 공개 여부