API v1 Documentation

현재 코드에 구현된 라우트와 요청/응답 형식을 기준으로 정리한 문서입니다.

Base URL: http://www.closetoya.com/api/v1
OpenAPI JSON: http://www.closetoya.com/api/v1/docs/openapi
인증: 보호된 엔드포인트는 Authorization: Bearer {access_token} 헤더가 필요합니다.

핵심 규칙

공개 조회 API와 검색 API는 인증 없이 접근할 수 있습니다. 외부에서 글을 쓰려면 먼저 간단 로그인으로 Bearer 토큰을 발급받고, 그 토큰으로 쓰기/수정/삭제 API를 호출하면 됩니다.

권장 인증 흐름

POST/login
외부 API 작성용 간단 로그인입니다. email, password 만으로 Passport Bearer 토큰을 발급합니다.
필드설명
email사용자 이메일
password사용자 비밀번호
token_name선택. 발급할 토큰 이름
POST/tokens
현재 유효한 Bearer 토큰으로 새 API 전용 토큰을 발급합니다. 실제 외부 연동에는 이 방식을 권장합니다.
필드설명
token_name선택. 새 토큰 이름
GET/token/validate
현재 Bearer 토큰이 유효하면 토큰 메타데이터와 사용자 정보를 반환합니다. 유효하지 않거나 만료되었으면 401 이 반환됩니다.
POST/token/reissue
현재 유효한 토큰을 폐기하고 새 Bearer 토큰을 다시 발급합니다. 외부 API 토큰 운영 중 교체가 필요할 때 사용하기 좋습니다.
필드설명
token_name선택. 새 토큰 이름
GET/tokens
현재 로그인 사용자 소유의 API 토큰 목록을 조회합니다. 토큰 문자열 자체는 다시 반환하지 않고 관리용 메타데이터만 반환합니다.
DELETE/tokens/{id}
내 API 토큰 하나를 폐기합니다. 즉시 Bearer 인증에 사용할 수 없게 됩니다.
POST/auth/logout
현재 access token을 revoke 합니다.
GET/user
현재 로그인한 사용자 정보를 반환합니다.
curl -X POST "http://www.closetoya.com/api/v1/login" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "password": "password123"
  }'
curl -X POST "http://www.closetoya.com/api/v1/tokens" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "token_name": "external-writer-prod"
  }'
curl -X POST "http://www.closetoya.com/api/v1/posts" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "외부 API로 쓴 글",
    "content": "# 제목

본문",
    "category_path": "api/external"
  }'

고급 OAuth 흐름

여기부터는 직접 OAuth client를 운영하는 경우에만 필요합니다. 일반적인 외부 글쓰기 자동화는 위의 /login 흐름을 권장합니다.
POST/auth/login
이메일/비밀번호와 OAuth client 정보로 access token, refresh token을 발급합니다.
필드설명
email사용자 이메일
password사용자 비밀번호
client_idOAuth client ID
client_secretOAuth client secret
grant_typepassword 고정
POST/auth/refresh
OAuth refresh token으로 access token을 갱신합니다.
필드설명
refresh_token기존 refresh token
client_idOAuth client ID
client_secretOAuth client secret
grant_typerefresh_token 고정
POST/auth/register
새 사용자를 생성합니다. 생성 직후 is_approved = false 상태이며 관리자 승인 전에는 로그인할 수 없습니다.
curl -X POST "http://www.closetoya.com/api/v1/auth/login" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "password": "password123",
    "client_id": "your-client-id",
    "client_secret": "your-client-secret",
    "grant_type": "password"
  }'

공개 포스트 조회

GET/posts
포스트 목록을 페이지네이션과 함께 반환합니다. 쿼리: per_page (기본 20)
GET/posts/{slug}
특정 포스트 전체 정보를 반환하고 조회수를 1 증가시킵니다.
GET/posts/{slug}/content
본문만 별도로 반환합니다. 응답 필드: content, content_type, last_modified
GET/random
숨김 처리되지 않은 글 중 하나를 랜덤 반환합니다.

포스트 쓰기

아래 엔드포인트는 모두 Bearer 토큰이 필요합니다.
POST/posts
새 포스트를 생성합니다.
필드필수설명
title포스트 제목
content마크다운 본문
slug아니오미입력 시 제목 기반 자동 생성
category_path아니오카테고리 경로
parent_slug아니오상위 문서 slug
PUT/posts/{id}
ID 기준 수정. 부분 업데이트가 가능하며 statusdraft/published 전환도 할 수 있습니다.
필드필수설명
title아니오변경할 포스트 제목
content아니오변경할 마크다운 본문
slug아니오변경할 slug. 다른 글과 중복될 수 없습니다.
status아니오draft 또는 published. 발행으로 바꿀 때 published_at 이 비어 있으면 현재 시각으로 채웁니다.
category_path아니오카테고리 경로
PUT/posts/slug/{slug}
slug 기준 수정. 요청 형식은 /posts/{id} 와 같고, 경로 파라미터만 slug를 사용합니다.
필드필수설명
title아니오변경할 포스트 제목
content아니오변경할 마크다운 본문
slug아니오변경할 slug. 다른 글과 중복될 수 없습니다.
status아니오draft 또는 published
category_path아니오카테고리 경로
DELETE/posts/{id}
ID 기준 삭제. 작성자 또는 관리자만 삭제할 수 있으며, 현재 구현은 삭제 후 POST /posts/{id}/unhide 로 복구할 수 있는 흐름과 함께 사용됩니다.
항목설명
인증Bearer 토큰 필요
권한포스트 작성자 또는 관리자만 가능
성공 응답{ "message": "포스트가 삭제되었습니다." }
실패 응답포스트가 없으면 404 post_not_found, 권한이 없으면 403 unauthorized
복구삭제 후 복구가 필요하면 POST /posts/{id}/unhide 를 사용
POST/posts/{id}/unhide
soft delete 된 포스트를 복구하고 is_hiddenfalse 로 되돌립니다.
항목설명
인증Bearer 토큰 필요
권한포스트 작성자 또는 관리자만 가능
성공 응답message 와 함께 id, title, slug, is_hidden=false 를 반환
실패 응답포스트가 없으면 404 post_not_found, 권한이 없으면 403 unauthorized
용도DELETE /posts/{id} 로 숨겨진 포스트를 다시 공개 상태로 되돌릴 때 사용
POST/posts/{parentSlug}/sub
부모 문서 아래 하위 문서를 생성합니다. 구현상 excerpt validation은 남아있지만 저장/응답에는 사용하지 않습니다.
curl -X PUT "http://www.closetoya.com/api/v1/posts/123" \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "수정된 제목",
    "content": "# 수정된 본문

내용을 바꿉니다.",
    "category_path": "dev/api",
    "status": "published"
  }'
curl -X PUT "http://www.closetoya.com/api/v1/posts/slug/existing-post-slug" \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "draft"
  }'
curl -X DELETE "http://www.closetoya.com/api/v1/posts/123" \
  -H "Authorization: Bearer ACCESS_TOKEN"
curl -X POST "http://www.closetoya.com/api/v1/posts/123/unhide" \
  -H "Authorization: Bearer ACCESS_TOKEN"

카테고리와 검색

GET/categories
공개 카테고리 목록을 반환합니다.
GET/categories/{path}
카테고리 상세와 포함된 포스트 목록을 반환합니다. 슬래시가 포함된 경로는 URL 인코딩이 필요할 수 있습니다.
GET/search
쿼리: q(필수), category, author, sort(relevance/date/views), per_page
GET/search/suggestions
q 길이가 2 미만이면 빈 배열을 반환합니다.

첨부파일과 고급 OAuth

POST/attachments
multipart/form-data 업로드. 필드: file, post_id, description
DELETE/attachments/{id}
업로더 또는 관리자만 삭제할 수 있습니다.
GET/oauth/clients
현재 로그인한 사용자가 소유한 고급 OAuth 연결 목록입니다.
POST/oauth/clients
현재 사용자 소유 고급 OAuth 연결을 생성합니다. 응답에서 secret은 생성 시점에만 반환됩니다.
GET/admin/clients
관리자 전용 전체 고급 OAuth 연결 목록
POST/admin/clients
관리자 전용 고급 OAuth 연결 생성
DELETE/admin/clients/{id}
관리자 전용 고급 OAuth 연결 revoke

응답 형식 예시

성공 예시

{
  "message": "포스트가 생성되었습니다.",
  "post": {
    "id": 12,
    "title": "새 글",
    "slug": "새-글",
    "category_path": "tech/api",
    "parent_id": null,
    "created_at": "2026-03-13T01:23:45.000000Z"
  }
}

검증 오류 예시

{
  "error": "validation_error",
  "message": "입력 데이터가 올바르지 않습니다.",
  "errors": {
    "title": [
      "The title field is required."
    ]
  }
}