1. Auth API

1.1. 회원 가입

POST /api/auth/signup HTTP/1.1
Content-Type: application/json
Content-Length: 96
Host: localhost:8080

{
  "email" : "user@example.com",
  "handle" : "twooter_123",
  "password" : "StrongP@ssw0rd!"
}

1.1.1. Request Fields

Path Type Optional Description

email

String

사용자 이메일

password

String

사용자 비밀번호 (영문 대/소문자, 숫자, 특수문자 포함 8자 이상)

handle

String

사용자 핸들 (영문, 숫자, 밑줄(_) 사용 가능, 4~50자)

1.1.2. HTTP Response

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 179

{
  "member" : {
    "id" : 123,
    "handle" : "twooter_123",
    "nickname" : "테이블 청소 마스터",
    "avatarPath" : "testpath",
    "email" : "user@example.com"
  }
}

1.1.3. Response Fields

Path Type Optional Description

member

Object

회원 정보

member.id

Number

회원 고유 ID

member.email

String

회원 이메일

member.handle

String

회원 핸들

member.nickname

String

회원 닉네임

member.avatarPath

String

회원 프로필 이미지 경로

1.2. 로그인

1.2.1. HTTP Request

POST /api/auth/signin HTTP/1.1
Content-Type: application/json
Content-Length: 64
Host: localhost:8080

{
  "handle" : "twooter_123",
  "password" : "StrongP@ssw0rd!"
}

1.2.2. Request Fields

Path Type Optional Description

handle

String

사용자 핸들 (영문, 숫자, 밑줄(_) 사용 가능, 4~50자)

password

String

사용자 비밀번호

1.2.3. HTTP Response

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 238

{
  "accessToken" : "<ACCESS_TOKEN>",
  "refreshToken" : "<REFRESH_TOKEN>",
  "member" : {
    "id" : 123,
    "handle" : "twooter_123",
    "nickname" : "twooter_123",
    "avatarPath" : "testpath",
    "email" : "user@example.com"
  }
}

1.2.4. Response Fields

Path Type Optional Description

accessToken

String

액세스 토큰 (JWT)

refreshToken

String

리프레시 토큰 (JWT)

member

Object

회원 정보

member.id

Number

회원 고유 ID

member.email

String

회원 이메일

member.handle

String

회원 핸들

member.nickname

String

회원 닉네임

member.avatarPath

String

회원 프로필 이미지 경로

1.3. 토큰 재발급

1.3.1. HTTP Request

POST /api/auth/reissue HTTP/1.1
Content-Type: application/json
Content-Length: 40
Host: localhost:8080

{
  "refreshToken" : "<REFRESH_TOKEN>"
}

1.3.2. Request Fields

Path Type Optional Description

refreshToken

String

유효한 리프레시 토큰 (JWT)

1.3.3. HTTP Response

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 84

{
  "accessToken" : "<NEW_ACCESS_TOKEN>",
  "refreshToken" : "<NEW_REFRESH_TOKEN>"
}

1.3.4. Response Fields

Path Type Optional Description

accessToken

String

새로 발급된 액세스 토큰 (JWT)

refreshToken

String

새로 발급된 리프레시 토큰 (JWT)

1.4. 로그아웃

1.4.1. HTTP Request

POST /api/auth/logout HTTP/1.1
Content-Type: application/json
Authorization: Bearer <ACCESS_TOKEN>
Host: localhost:8080

1.4.2. Request Headers

Name Description Optional

Authorization

Bearer 인증 토큰

1.4.3. HTTP Response

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 34

{
  "userHandle" : "user_handle"
}

1.4.4. Response Fields

Path Type Optional Description

userHandle

String

로그아웃된 유저의 핸들 (토큰이 유효하지 않거나 처리 중 오류가 발생한 경우 'unknown' 반환)

2. Member API

2.2. 팔로우

유저를 팔로우한다.

2.2.1. HTTP Request

POST /api/members/follow HTTP/1.1
Content-Type: application/json
Authorization: Bearer <ACCESS_TOKEN>
Content-Length: 26
Host: localhost:8080

{
  "targetMemberId" : 1
}

2.2.2. Request Headers

Name Description Optional

Authorization

인증 토큰

2.2.3. Request Fields

Path Type Optional Description

targetMemberId

Number

팔로우할 대상 멤버의 ID

2.2.4. HTTP Response

HTTP/1.1 201 Created
Content-Type: application/json
Content-Length: 66

{
  "targetMemberId" : 1,
  "followedAt" : "2025-05-05T10:10:00"
}

2.2.5. Response Fields

Path Type Optional Description

targetMemberId

Number

팔로우한 대상 멤버의 ID

followedAt

String

팔로우한 시간

2.3. 언팔로우

유저의 팔로우를 취소한다.

2.3.1. HTTP Request

DELETE /api/members/follow/1 HTTP/1.1
Authorization: Bearer <ACCESS_TOKEN>
Host: localhost:8080

2.3.2. Request Headers

Name Description Optional

Authorization

인증 토큰

2.3.3. Path Parameters

Parameter Description Optional

targetMemberId

언팔로우할 대상 멤버의 ID

2.3.4. HTTP Response

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 75

{
  "targetMemberId" : 1,
  "unfollowedAt" : "2025-06-13T13:36:18.687462"
}

2.3.5. Response Fields

Path Type Optional Description

targetMemberId

Number

언팔로우한 대상 멤버의 ID

unfollowedAt

String

언팔로우한 시간

2.4. 팔로워 목록

해당 유저를 팔로우 하고 있는 유저(팔로워) 목록을 조회한다.

로그인 했을 경우, 그 유저와의 관계도 함께 조회된다.

2.4.1. HTTP Request

GET /api/members/1/followers?cursor=dXNlcjpVMDYxTkZUVDI%3D&limit=10 HTTP/1.1
Content-Type: application/json
Host: localhost:8080

2.4.2. Path Parameters

Parameter Description Optional

memberId

팔로워 목록을 조회할 멤버의 ID

2.4.3. Query Parameters

Parameter Description Optional

cursor

이전 요청의 response 메타 데이터가 반환한 next_cursor 의 속성 (아무 값이 없을 경우 컬렉션이 첫 번째 페이지를 가져옴)

true

limit

조회할 최대 팔로워 수 (기본값: 20, 최소값: 1)

true

2.4.4. HTTP Response

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 779

{
  "members" : [ {
    "id" : 1,
    "handle" : "cameraman",
    "nickname" : "카메라맨",
    "avatarPath" : "test_avatar_path.jpg",
    "bio" : "카메라맨의 바이오",
    "followsMe" : false,
    "followingByMe" : false
  }, {
    "id" : 2,
    "handle" : "movie_journalist",
    "nickname" : "영화 상영 기자",
    "avatarPath" : "test_avatar_path.jpg",
    "bio" : "영화 상영 기자의 바이오",
    "followsMe" : false,
    "followingByMe" : false
  }, {
    "id" : 3,
    "handle" : "movie_critic",
    "nickname" : "영화 평론가",
    "avatarPath" : "test_avatar_path.jpg",
    "bio" : "영화 평론가의 바이오",
    "followsMe" : false,
    "followingByMe" : true
  } ],
  "metadata" : {
    "nextCursor" : null,
    "hasNext" : false
  }
}

2.4.5. Response Fields

Path Type Optional Description

members

Array

팔로워 목록

metadata

Object

페이지네이션 메타데이터

members[].id

Number

팔로워의 ID

members[].handle

String

팔로워의 핸들

members[].nickname

String

팔로워의 닉네임

members[].avatarPath

String

팔로워의 아바타 이미지 경로

members[].bio

String

팔로워의 바이오

members[].followingByMe

Boolean

내가 이 팔로워를 팔로우하고 있는지 여부

members[].followsMe

Boolean

이 팔로워가 나를 팔로우하고 있는지 여부

metadata.nextCursor

String

true

다음 페이지를 가져오는 데 사용될 커서

metadata.hasNext

Boolean

다음 페이지가 존재하는지 여부

2.5. 팔로잉 목록

해당 유저가 팔로우하고 있는 유저(팔로잉) 목록을 조회한다. 로그인 했을 경우, 그 유저와의 관계도 함께 조회된다.

2.5.1. HTTP Request

GET /api/members/1/followings?cursor=dXNlcjpVMDYxTkZUVDI%3D&limit=10 HTTP/1.1
Content-Type: application/json
Host: localhost:8080

2.5.2. Path Parameters

Parameter Description Optional

memberId

팔로잉 목록을 조회할 멤버의 ID

2.5.3. Query Parameters

Parameter Description Optional

cursor

이전 요청의 response 메타 데이터가 반환한 next_cursor 의 속성 (아무 값이 없을 경우 컬렉션이 첫 번째 페이지를 가져옴)

true

limit

조회할 최대 팔로잉 수 (기본값: 20, 최소값: 1)

true

2.5.4. HTTP Response

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 779

{
  "members" : [ {
    "id" : 1,
    "handle" : "cameraman",
    "nickname" : "카메라맨",
    "avatarPath" : "test_avatar_path.jpg",
    "bio" : "카메라맨의 바이오",
    "followsMe" : false,
    "followingByMe" : false
  }, {
    "id" : 2,
    "handle" : "movie_journalist",
    "nickname" : "영화 상영 기자",
    "avatarPath" : "test_avatar_path.jpg",
    "bio" : "영화 상영 기자의 바이오",
    "followsMe" : false,
    "followingByMe" : false
  }, {
    "id" : 3,
    "handle" : "movie_critic",
    "nickname" : "영화 평론가",
    "avatarPath" : "test_avatar_path.jpg",
    "bio" : "영화 평론가의 바이오",
    "followsMe" : false,
    "followingByMe" : true
  } ],
  "metadata" : {
    "nextCursor" : null,
    "hasNext" : false
  }
}

2.5.5. Response Fields

Path Type Optional Description

members

Array

팔로잉 목록

metadata

Object

페이지네이션 메타데이터

members[].id

Number

팔로워의 ID

members[].handle

String

팔로워의 핸들

members[].nickname

String

팔로워의 닉네임

members[].avatarPath

String

팔로워의 아바타 이미지 경로

members[].bio

String

팔로워의 바이오

members[].followingByMe

Boolean

내가 이 팔로워를 팔로우하고 있는지 여부

members[].followsMe

Boolean

이 팔로워가 나를 팔로우하고 있는지 여부

metadata.nextCursor

String

true

다음 페이지를 가져오는 데 사용될 커서

metadata.hasNext

Boolean

다음 페이지가 존재하는지 여부

3. Timeline API

3.1. 홈 타임라인 조회

현재 로그인한 유저의 홈 타임라인을 조회한다.

타임라인에는 자신의 포스트와 리포스트, 팔로우하는 유저의 포스트와 리포스트가 포함된다.

3.1.1. HTTP Request

GET /api/timeline/home?cursor=dXNlcjpVMDYxTkZUVDI%3D&limit=10 HTTP/1.1
Content-Type: application/json
Authorization: Bearer <ACCESS_TOKEN>
Host: localhost:8080

3.1.2. Request Headers

Name Description Optional

Authorization

액세스 토큰 (Bearer 타입)

3.1.3. Query Parameters

Parameter Description Optional

cursor

이전 요청의 response 메타 데이터가 반환한 next_cursor 의 속성 (아무 값이 없을 경우 컬렉션이 첫 번째 페이지를 가져옴)

true

limit

페이지당 반환될 아이템 수 (기본값: 20)

true

3.1.4. HTTP Response

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 1884

{
  "timeline" : [ {
    "type" : "post",
    "createdAt" : "2025-05-05T00:00:00",
    "post" : {
      "id" : 1,
      "author" : {
        "id" : 1,
        "handle" : "table_cleaner",
        "nickname" : "테이블 청소 마스터",
        "avatarPath" : "https://cdn.twooter.xyz/media/avatar1"
      },
      "content" : "새 책상을 정리하다가 유용해 보이는 오래된 자료를 발견해서 이메일로 보냅니다.",
      "likeCount" : 15,
      "repostCount" : 3,
      "mediaEntities" : [ {
        "mediaId" : 101,
        "mediaUrl" : "https://cdn.twooter.xyz/media/101.jpg"
      }, {
        "mediaId" : 102,
        "mediaUrl" : "https://cdn.twooter.xyz/media/102.jpg"
      } ],
      "createdAt" : "2025-05-05T00:00:00",
      "liked" : true,
      "reposted" : true,
      "deleted" : false
    }
  }, {
    "type" : "repost",
    "createdAt" : "2025-05-05T05:05:00",
    "post" : {
      "id" : 2,
      "author" : {
        "id" : 2,
        "handle" : "table_specialist",
        "nickname" : "친절한 책상 정리 전문가",
        "avatarPath" : "https://cdn.twooter.xyz/media/avatar2"
      },
      "content" : "비고: 누가 남긴 자료인지 모르겠네요.",
      "likeCount" : 15,
      "repostCount" : 3,
      "mediaEntities" : [ {
        "mediaId" : 103,
        "mediaUrl" : "https://cdn.twooter.xyz/media/101.jpg"
      }, {
        "mediaId" : 104,
        "mediaUrl" : "https://cdn.twooter.xyz/media/102.jpg"
      } ],
      "createdAt" : "2025-05-05T10:10:00",
      "liked" : true,
      "reposted" : false,
      "deleted" : false
    },
    "repostBy" : {
      "id" : 1,
      "handle" : "table_cleaner",
      "nickname" : "테이블 청소 마스터",
      "avatarPath" : "https://cdn.twooter.xyz/media/avatar1"
    }
  } ],
  "metadata" : {
    "nextCursor" : "dGVhbTpDMDYxRkE1UEI=",
    "hasNext" : true
  }
}

3.1.5. Response Fields

Path Type Optional Description

timeline

Array

타임라인 아이템 목록

metadata

Object

페이지네이션 메타데이터

timeline[].type

String

타임라인 아이템 타입 (post 또는 repost)

timeline[].createdAt

String

타임라인 아이템 생성 시간 (리포스트의 경우 리포스트한 시간)

timeline[].post

Object

포스트 정보 (원본 포스트 또는 리포스트된 포스트)

timeline[].repostBy

Object

true

리포스트한 사용자 정보 (type이 'repost’일 때만 존재)

timeline[].post.id

Number

포스트 Id

timeline[].post.author

Object

포스트 작성자 정보

timeline[].post.content

String

포스트 내용

timeline[].post.likeCount

Number

좋아요 수

timeline[].post.repostCount

Number

리포스트 수

timeline[].post.mediaEntities

Array

첨부된 미디어 정보 목록

timeline[].post.createdAt

String

포스트 생성 시간

timeline[].post.liked

Boolean

현재 사용자의 좋아요 여부

timeline[].post.reposted

Boolean

현재 사용자의 리포스트 여부

timeline[].post.deleted

Boolean

삭제 여부

timeline[].post.author.id

Number

사용자 고유 ID

timeline[].post.author.handle

String

사용자 핸들

timeline[].post.author.nickname

String

사용자 닉네임

timeline[].post.author.avatarPath

String

사용자 아바타 URL

timeline[].post.mediaEntities[].mediaId

Number

미디어 ID

timeline[].post.mediaEntities[].mediaUrl

String

미디어 경로 URL

timeline[].repostBy.id

Number

사용자 고유 ID

timeline[].repostBy.handle

String

사용자 핸들

timeline[].repostBy.nickname

String

사용자 닉네임

timeline[].repostBy.avatarPath

String

사용자 아바타 URL

metadata.nextCursor

String

true

다음 페이지를 가져오는 데 사용될 커서

metadata.hasNext

Boolean

다음 페이지가 존재하는지 여부

3.2. 나의 타임라인 조회

현재 로그인한 유저의 타임라인을 조회한다.

타임라인은 자신의 포스트 + 리포스트로 구성된다.

3.2.1. HTTP Request

GET /api/timeline/me?cursor=dXNlcjpVMDYxTkZUVDI%3D&limit=10 HTTP/1.1
Content-Type: application/json
Authorization: Bearer <ACCESS_TOKEN>
Host: localhost:8080

3.2.2. Request Headers

Name Description Optional

Authorization

액세스 토큰 (Bearer 타입)

3.2.3. Query Parameters

Parameter Description Optional

cursor

이전 요청의 response 메타 데이터가 반환한 next_cursor 의 속성 (아무 값이 없을 경우 컬렉션이 첫 번째 페이지를 가져옴)

true

limit

페이지당 반환될 아이템 수 (기본값: 20)

true

3.2.4. HTTP Response

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 1884

{
  "timeline" : [ {
    "type" : "post",
    "createdAt" : "2025-05-05T00:00:00",
    "post" : {
      "id" : 1,
      "author" : {
        "id" : 1,
        "handle" : "table_cleaner",
        "nickname" : "테이블 청소 마스터",
        "avatarPath" : "https://cdn.twooter.xyz/media/avatar1"
      },
      "content" : "새 책상을 정리하다가 유용해 보이는 오래된 자료를 발견해서 이메일로 보냅니다.",
      "likeCount" : 15,
      "repostCount" : 3,
      "mediaEntities" : [ {
        "mediaId" : 101,
        "mediaUrl" : "https://cdn.twooter.xyz/media/101.jpg"
      }, {
        "mediaId" : 102,
        "mediaUrl" : "https://cdn.twooter.xyz/media/102.jpg"
      } ],
      "createdAt" : "2025-05-05T00:00:00",
      "liked" : true,
      "reposted" : true,
      "deleted" : false
    }
  }, {
    "type" : "repost",
    "createdAt" : "2025-05-05T05:05:00",
    "post" : {
      "id" : 2,
      "author" : {
        "id" : 2,
        "handle" : "table_specialist",
        "nickname" : "친절한 책상 정리 전문가",
        "avatarPath" : "https://cdn.twooter.xyz/media/avatar2"
      },
      "content" : "비고: 누가 남긴 자료인지 모르겠네요.",
      "likeCount" : 15,
      "repostCount" : 3,
      "mediaEntities" : [ {
        "mediaId" : 103,
        "mediaUrl" : "https://cdn.twooter.xyz/media/101.jpg"
      }, {
        "mediaId" : 104,
        "mediaUrl" : "https://cdn.twooter.xyz/media/102.jpg"
      } ],
      "createdAt" : "2025-05-05T10:10:00",
      "liked" : true,
      "reposted" : false,
      "deleted" : false
    },
    "repostBy" : {
      "id" : 1,
      "handle" : "table_cleaner",
      "nickname" : "테이블 청소 마스터",
      "avatarPath" : "https://cdn.twooter.xyz/media/avatar1"
    }
  } ],
  "metadata" : {
    "nextCursor" : "dGVhbTpDMDYxRkE1UEI=",
    "hasNext" : true
  }
}

3.2.5. Response Fields

Path Type Optional Description

timeline

Array

타임라인 아이템 목록

metadata

Object

페이지네이션 메타데이터

timeline[].type

String

타임라인 아이템 타입 (post 또는 repost)

timeline[].createdAt

String

타임라인 아이템 생성 시간 (리포스트의 경우 리포스트한 시간)

timeline[].post

Object

포스트 정보 (원본 포스트 또는 리포스트된 포스트)

timeline[].repostBy

Object

true

리포스트한 사용자 정보 (type이 'repost’일 때만 존재)

timeline[].post.id

Number

포스트 Id

timeline[].post.author

Object

포스트 작성자 정보

timeline[].post.content

String

포스트 내용

timeline[].post.likeCount

Number

좋아요 수

timeline[].post.repostCount

Number

리포스트 수

timeline[].post.mediaEntities

Array

첨부된 미디어 정보 목록

timeline[].post.createdAt

String

포스트 생성 시간

timeline[].post.liked

Boolean

현재 사용자의 좋아요 여부

timeline[].post.reposted

Boolean

현재 사용자의 리포스트 여부

timeline[].post.deleted

Boolean

삭제 여부

timeline[].post.author.id

Number

사용자 고유 ID

timeline[].post.author.handle

String

사용자 핸들

timeline[].post.author.nickname

String

사용자 닉네임

timeline[].post.author.avatarPath

String

사용자 아바타 URL

timeline[].post.mediaEntities[].mediaId

Number

미디어 ID

timeline[].post.mediaEntities[].mediaUrl

String

미디어 경로 URL

timeline[].repostBy.id

Number

사용자 고유 ID

timeline[].repostBy.handle

String

사용자 핸들

timeline[].repostBy.nickname

String

사용자 닉네임

timeline[].repostBy.avatarPath

String

사용자 아바타 URL

metadata.nextCursor

String

true

다음 페이지를 가져오는 데 사용될 커서

metadata.hasNext

Boolean

다음 페이지가 존재하는지 여부

3.3. 특정 유저 ID의 타임라인 조회

특정 유저의 타임라인을 조회한다.

타임라인은 해당 유저의 포스트 + 해당 유저의 리포스트로 구성된다.

3.3.1. HTTP Request

GET /api/timeline/user/1?cursor=dXNlcjpVMDYxTkZUVDI%3D&limit=10 HTTP/1.1
Content-Type: application/json
Host: localhost:8080

3.3.2. Path Parameters

Parameter Description Optional

userId

조회 대상 유저의 ID

3.3.3. HTTP Response

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 1884

{
  "timeline" : [ {
    "type" : "post",
    "createdAt" : "2025-05-05T00:00:00",
    "post" : {
      "id" : 1,
      "author" : {
        "id" : 1,
        "handle" : "table_cleaner",
        "nickname" : "테이블 청소 마스터",
        "avatarPath" : "https://cdn.twooter.xyz/media/avatar1"
      },
      "content" : "새 책상을 정리하다가 유용해 보이는 오래된 자료를 발견해서 이메일로 보냅니다.",
      "likeCount" : 15,
      "repostCount" : 3,
      "mediaEntities" : [ {
        "mediaId" : 101,
        "mediaUrl" : "https://cdn.twooter.xyz/media/101.jpg"
      }, {
        "mediaId" : 102,
        "mediaUrl" : "https://cdn.twooter.xyz/media/102.jpg"
      } ],
      "createdAt" : "2025-05-05T00:00:00",
      "liked" : true,
      "reposted" : true,
      "deleted" : false
    }
  }, {
    "type" : "repost",
    "createdAt" : "2025-05-05T05:05:00",
    "post" : {
      "id" : 2,
      "author" : {
        "id" : 2,
        "handle" : "table_specialist",
        "nickname" : "친절한 책상 정리 전문가",
        "avatarPath" : "https://cdn.twooter.xyz/media/avatar2"
      },
      "content" : "비고: 누가 남긴 자료인지 모르겠네요.",
      "likeCount" : 15,
      "repostCount" : 3,
      "mediaEntities" : [ {
        "mediaId" : 103,
        "mediaUrl" : "https://cdn.twooter.xyz/media/101.jpg"
      }, {
        "mediaId" : 104,
        "mediaUrl" : "https://cdn.twooter.xyz/media/102.jpg"
      } ],
      "createdAt" : "2025-05-05T10:10:00",
      "liked" : true,
      "reposted" : false,
      "deleted" : false
    },
    "repostBy" : {
      "id" : 1,
      "handle" : "table_cleaner",
      "nickname" : "테이블 청소 마스터",
      "avatarPath" : "https://cdn.twooter.xyz/media/avatar1"
    }
  } ],
  "metadata" : {
    "nextCursor" : "dGVhbTpDMDYxRkE1UEI=",
    "hasNext" : true
  }
}

3.3.4. Response Fields

Path Type Optional Description

timeline

Array

타임라인 아이템 목록

metadata

Object

페이지네이션 메타데이터

timeline[].type

String

타임라인 아이템 타입 (post 또는 repost)

timeline[].createdAt

String

타임라인 아이템 생성 시간 (리포스트의 경우 리포스트한 시간)

timeline[].post

Object

포스트 정보 (원본 포스트 또는 리포스트된 포스트)

timeline[].repostBy

Object

true

리포스트한 사용자 정보 (type이 'repost’일 때만 존재)

timeline[].post.id

Number

포스트 Id

timeline[].post.author

Object

포스트 작성자 정보

timeline[].post.content

String

포스트 내용

timeline[].post.likeCount

Number

좋아요 수

timeline[].post.repostCount

Number

리포스트 수

timeline[].post.mediaEntities

Array

첨부된 미디어 정보 목록

timeline[].post.createdAt

String

포스트 생성 시간

timeline[].post.liked

Boolean

현재 사용자의 좋아요 여부

timeline[].post.reposted

Boolean

현재 사용자의 리포스트 여부

timeline[].post.deleted

Boolean

삭제 여부

timeline[].post.author.id

Number

사용자 고유 ID

timeline[].post.author.handle

String

사용자 핸들

timeline[].post.author.nickname

String

사용자 닉네임

timeline[].post.author.avatarPath

String

사용자 아바타 URL

timeline[].post.mediaEntities[].mediaId

Number

미디어 ID

timeline[].post.mediaEntities[].mediaUrl

String

미디어 경로 URL

timeline[].repostBy.id

Number

사용자 고유 ID

timeline[].repostBy.handle

String

사용자 핸들

timeline[].repostBy.nickname

String

사용자 닉네임

timeline[].repostBy.avatarPath

String

사용자 아바타 URL

metadata.nextCursor

String

true

다음 페이지를 가져오는 데 사용될 커서

metadata.hasNext

Boolean

다음 페이지가 존재하는지 여부

4. Post API

4.1. 포스트 작성

4.1.1. HTTP Request

POST /api/posts HTTP/1.1
Content-Type: application/json
Authorization: Bearer <ACCESS_TOKEN>
Content-Length: 190
Host: localhost:8080

{
  "content" : "트우터에 올릴 새로운 포스트 내용입니다. #첫글 #환영",
  "media" : [ "https://cdn.twooter.xyz/media/101.jpg", "https://cdn.twooter.xyz/media/102.jpg" ]
}

4.1.2. Request Headers

Name Description Optional

Authorization

액세스 토큰 (Bearer 타입)

4.1.3. Request Fields

Path Type Optional Description

content

String

포스트 내용 (미디어가 없는 경우 필수, 최대 500자)

media

Array

true

첨부된 미디어 파일 URL (내용이 비어있는 경우 필수, 최대 4개), 파일 업로드 API를 이용해, 업로드 후 링크를 첨부

4.1.4. HTTP Response

HTTP/1.1 201 Created
Location: http://localhost:8080/api/posts
Content-Type: application/json
Content-Length: 533

{
  "id" : 1,
  "content" : "트우터에 올릴 새로운 포스트 내용입니다. #첫글 #환영",
  "author" : {
    "id" : 123,
    "handle" : "table_cleaner",
    "nickname" : "테이블 청소 마스터",
    "avatarPath" : "https://cdn.twooter.xyz/media/avatar",
    "email" : "test@test.com"
  },
  "media" : [ {
    "mediaId" : 101,
    "mediaUrl" : "https://cdn.twooter.xyz/media/101.jpg"
  }, {
    "mediaId" : 102,
    "mediaUrl" : "https://cdn.twooter.xyz/media/102.jpg"
  } ],
  "createdAt" : "2025-05-05T00:00:00"
}

4.1.5. Response Fields

Path Type Optional Description

id

Number

생성된 포스트 ID

content

String

포스트 내용

author

Object

포스트 작성자 정보

media

Array

첨부된 미디어 정보 목록

createdAt

String

포스트 생성 시간

author.id

Number

작성자 고유 ID

author.nickname

String

작성자 닉네임

author.avatarPath

String

작성자 아바타 URL

author.handle

String

작성자 핸들

author.email

String

작성자 이메일

media[].mediaId

Number

미디어 ID

media[].mediaUrl

String

미디어 접근 URL

4.2. 포스트 단건 조회

포스트를 단건 조회한다.

4.2.1. HTTP Request

GET /api/posts/1 HTTP/1.1
Host: localhost:8080

4.2.2. Path Parameters

Parameter Description Optional

postId

조회할 포스트 ID

4.2.3. HTTP Response

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 649

{
  "id" : 1,
  "author" : {
    "id" : 123,
    "handle" : "table_cleaner",
    "nickname" : "테이블 청소 마스터",
    "avatarPath" : "https://cdn.twooter.xyz/media/avatar"
  },
  "content" : "새 책상을 정리하다가 유용해 보이는 오래된 자료를 발견해서 이메일로 보냅니다.",
  "likeCount" : 15,
  "repostCount" : 3,
  "mediaEntities" : [ {
    "mediaId" : 101,
    "mediaUrl" : "https://cdn.twooter.xyz/media/101.jpg"
  }, {
    "mediaId" : 102,
    "mediaUrl" : "https://cdn.twooter.xyz/media/102.jpg"
  } ],
  "createdAt" : "2025-05-05T00:00:00",
  "liked" : true,
  "reposted" : false,
  "deleted" : false
}

4.2.4. Response Fields

Path Type Optional Description

id

Number

포스트 Id

content

String

포스트 내용

author

Object

포스트 작성자 정보

likeCount

Number

좋아요 수

liked

Boolean

현재 사용자의 좋아요 여부

repostCount

Number

리포스트 수

reposted

Boolean

현재 사용자의 리포스트 여부

mediaEntities

Array

첨부된 미디어 정보 목록

createdAt

String

포스트 생성 시간

deleted

Boolean

삭제 여부

author.id

Number

작성자 고유 ID

author.nickname

String

작성자 닉네임

author.avatarPath

String

작성자 아바타 URL

author.handle

String

작성자 핸들

mediaEntities[].mediaId

Number

미디어 ID

mediaEntities[].mediaUrl

String

미디어 접근 URL

4.3. 포스트 좋아요 / 좋아요 취소

포스트에 좋아요를 누르거나, 이미 좋아요가 눌러진 상태에서 다시 누르면 좋아요를 취소한다.

4.3.1. HTTP Request

PATCH /api/posts/1/like HTTP/1.1
Authorization: Bearer <ACCESS_TOKEN>
Host: localhost:8080
Content-Type: application/x-www-form-urlencoded

4.3.2. Request Headers

Name Description Optional

Authorization

액세스 토큰 (Bearer 타입)

4.3.3. Path Parameters

Parameter Description Optional

postId

좋아요/좋아요 취소할 포스트 ID

4.3.4. HTTP Response

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 38

{
  "postId" : 1,
  "isLiked" : true
}

4.3.5. Response Fields

Path Type Optional Description

postId

Number

포스트 ID

isLiked

Boolean

현재 사용자의 좋아요 여부

4.4. 리포스트

포스트를 리포스트한다.

4.4.1. HTTP Request

POST /api/posts/1/repost HTTP/1.1
Authorization: Bearer <ACCESS_TOKEN>
Host: localhost:8080
Content-Type: application/x-www-form-urlencoded

4.4.2. Request Headers

Name Description Optional

Authorization

액세스 토큰 (Bearer 타입)

4.4.3. Path Parameters

Parameter Description Optional

postId

리포스트할 대상 포스트 ID

4.4.4. HTTP Response

HTTP/1.1 201 Created
Location: http://localhost:8080/api/posts/1/repost
Content-Type: application/json
Content-Length: 84

{
  "repostId" : 1,
  "originalPostId" : 2,
  "repostedAt" : "2025-05-05T00:00:00"
}

4.4.5. Response Fields

Path Type Optional Description

repostId

Number

생성된 리포스트 ID

originalPostId

Number

원본 포스트 ID

repostedAt

String

리포스트 생성 시간

4.5. 답글 작성

포스트에 대한 답글을 작성한다.

4.5.1. HTTP Request

POST /api/posts/replies HTTP/1.1
Content-Type: application/json
Authorization: Bearer <ACCESS_TOKEN>
Content-Length: 118
Host: localhost:8080

{
  "content" : "답글 내용입니다.",
  "media" : [ "https://cdn.twooter.xyz/media/201.jpg" ],
  "parentId" : 1
}

4.5.2. Request Headers

Name Description Optional

Authorization

액세스 토큰 (Bearer 타입)

4.5.3. Request Fields

Path Type Optional Description

parentId

Number

답글의 부모 포스트 ID (필수)

content

String

포스트 내용 (미디어가 없는 경우 필수, 최대 500자)

media

Array

true

첨부된 미디어 파일 URL (내용이 비어있는 경우 필수, 최대 4개), 파일 업로드 API를 이용해, 업로드 후 링크를 첨부

4.5.4. HTTP Response

HTTP/1.1 201 Created
Location: http://localhost:8080/api/posts/replies
Content-Type: application/json
Content-Length: 417

{
  "id" : 2,
  "content" : "답글 내용입니다.",
  "author" : {
    "id" : 123,
    "handle" : "table_cleaner",
    "nickname" : "테이블 청소 마스터",
    "avatarPath" : "https://cdn.twooter.xyz/media/avatar",
    "email" : "test@test.com"
  },
  "media" : [ {
    "mediaId" : 201,
    "mediaUrl" : "https://cdn.twooter.xyz/media/201.jpg"
  } ],
  "createdAt" : "2025-05-05T00:00:00",
  "parentId" : 1
}

4.5.5. Response Fields

Path Type Optional Description

id

Number

생성된 포스트 ID

content

String

포스트 내용

author

Object

포스트 작성자 정보

media

Array

첨부된 미디어 정보 목록

createdAt

String

포스트 생성 시간

parentId

Number

생성된 답글의 부모 ID

author.id

Number

작성자 고유 ID

author.nickname

String

작성자 닉네임

author.avatarPath

String

작성자 아바타 URL

author.handle

String

작성자 핸들

author.email

String

작성자 이메일

media[].mediaId

Number

미디어 ID

media[].mediaUrl

String

미디어 접근 URL

4.6. 답글 조회

포스트 ID에 대한 답글을 조회한다. 이 때 답글은 level 1로 조회되며, 오래된 순으로 정렬된다.

4.6.1. HTTP Request

GET /api/posts/1/replies HTTP/1.1
Host: localhost:8080

4.6.2. Path Parameters

Parameter Description Optional

postId

답글을 조회할 부모 포스트 ID

4.6.3. HTTP Response

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 1785

{
  "posts" : [ {
    "id" : 2,
    "author" : {
      "id" : 456,
      "handle" : "reply_author_2",
      "nickname" : "답글 작성자",
      "avatarPath" : "https://cdn.twooter.xyz/media/avatar_reply.jpg"
    },
    "content" : "이것은 포스트 1에 대한 답글입니다. 답글 ID: 2",
    "likeCount" : 7,
    "repostCount" : 2,
    "mediaEntities" : [ {
      "mediaId" : 202,
      "mediaUrl" : "https://cdn.twooter.xyz/media/reply_2.jpg"
    } ],
    "createdAt" : "2025-05-05T00:02:00",
    "liked" : true,
    "reposted" : false,
    "deleted" : false
  }, {
    "id" : 3,
    "author" : {
      "id" : 456,
      "handle" : "reply_author_3",
      "nickname" : "답글 작성자",
      "avatarPath" : "https://cdn.twooter.xyz/media/avatar_reply.jpg"
    },
    "content" : "이것은 포스트 1에 대한 답글입니다. 답글 ID: 3",
    "likeCount" : 8,
    "repostCount" : 2,
    "mediaEntities" : [ {
      "mediaId" : 203,
      "mediaUrl" : "https://cdn.twooter.xyz/media/reply_3.jpg"
    } ],
    "createdAt" : "2025-05-05T00:03:00",
    "liked" : false,
    "reposted" : false,
    "deleted" : false
  }, {
    "id" : 4,
    "author" : {
      "id" : 456,
      "handle" : "reply_author_4",
      "nickname" : "답글 작성자",
      "avatarPath" : "https://cdn.twooter.xyz/media/avatar_reply.jpg"
    },
    "content" : "이것은 포스트 1에 대한 답글입니다. 답글 ID: 4",
    "likeCount" : 9,
    "repostCount" : 2,
    "mediaEntities" : [ {
      "mediaId" : 204,
      "mediaUrl" : "https://cdn.twooter.xyz/media/reply_4.jpg"
    } ],
    "createdAt" : "2025-05-05T00:04:00",
    "liked" : true,
    "reposted" : false,
    "deleted" : false
  } ],
  "metadata" : {
    "nextCursor" : "dGVhbTpDMDYxRkE1UEI=",
    "hasNext" : true
  }
}

4.6.4. Response Fields

Path Type Optional Description

posts

Array

답글 포스트 목록

metadata

Object

페이지네이션 메타데이터

posts[].id

Number

답글 포스트 ID

posts[].author

Object

답글 작성자 정보

posts[].content

String

답글 내용

posts[].likeCount

Number

좋아요 수

posts[].liked

Boolean

현재 사용자의 좋아요 여부

posts[].repostCount

Number

리포스트 수

posts[].reposted

Boolean

현재 사용자의 리포스트 여부

posts[].mediaEntities

Array

첨부된 미디어 정보 목록

posts[].createdAt

String

답글 생성 시간

posts[].deleted

Boolean

삭제 여부

posts[].author.id

Number

작성자 고유 ID

posts[].author.nickname

String

작성자 닉네임

posts[].author.avatarPath

String

작성자 아바타 URL

posts[].author.handle

String

작성자 핸들

posts[].mediaEntities[].mediaId

Number

미디어 ID

posts[].mediaEntities[].mediaUrl

String

미디어 접근 URL

metadata.nextCursor

String

true

다음 페이지를 가져오는 데 사용될 커서

metadata.hasNext

Boolean

다음 페이지가 존재하는지 여부

5. Media API

5.1. 파일 Upload URL 발급

5.1.1. HTTP Request

GET /api/media/upload-url?filename=test.jpg&contentType=image%2Fjpeg HTTP/1.1
Authorization: Bearer <ACCESS_TOKEN>
Host: localhost:8080

5.1.2. Query Parameters

Parameter Description Optional

filename

업로드할 파일명 (예: test.jpg)

contentType

파일의 MIME 타입 (예: image/jpeg)

5.1.3. HTTP Response

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 89

{
  "url" : "https://storage.googleapis.com/your-bucket/media/abc123.jpg?signature=..."
}

5.1.4. Response Fields

Path Type Optional Description

url

String

GCS에 직접 업로드 가능한 signed URL