ZUKU API — Authentication
계정 가입·로그인·세션·캡차·개발자 API 키 계약입니다. 본 문서는 backend/rs/src/router.rs의 실제 경로와 일치합니다.
Base URL
| 환경 | Base URL |
|---|---|
| 프로덕션 | https://zuzunza.com/api/v1 |
| 로컬 | http://localhost:3001/api/v1 |
목차
- 인증 모델
- 응답 봉투
- 엔드포인트 표
- Captcha
- Register / Signup
- Login
- Logout / Refresh
- Me (프로필)
- Sessions
- OAuth
- Developer API Keys
- curl · JS/TS 예제
- 오류 코드 요약
1. 인증 모델
ZUKU API는 두 가지 자격 증명을 사용합니다.
| 방식 | 헤더 | 용도 |
|---|---|---|
| Bearer session | Authorization: Bearer <access_token> |
로그인 세션. 대부분의 쓰기 API·프로필·세션·개발자 키 관리에 필요 |
| X-API-Key | X-API-Key: sk_live_… |
개발자 포털에서 발급한 키. sk_live_ 접두사 필수. Bearer가 없을 때 일부 서버-투-서버 경로(예: 콘텐츠 생성)에서 발급자를 해석 |
규칙 요약:
- 세션 토큰은
POST /auth/login또는POST /auth/register응답의tokens.access_token/tokens.refresh_token입니다. token_type은 항상"Bearer"입니다.- 개발자 키 원문(
key)은 발급 직후 한 번만 내려갑니다. 이후GET /developer/keys에는 메타데이터만 있습니다. - 소셜·알림·DM·세션 관리 등 본 문서의 대부분 경로는 Bearer 세션만 허용합니다 (
require_authenticated_user).
2. 응답 봉투
성공:
{
"success": true,
"data": { },
"meta": {
"request_id": "…",
"timestamp": "2026-08-22T04:00:00Z",
"version": "v1"
}
}
실패:
{
"success": false,
"error": {
"code": "UNAUTHORIZED",
"message": "…",
"details": [{ "field": "password", "message": "…" }]
},
"meta": { "request_id": "…", "timestamp": "…", "version": "v1" }
}
details는 검증 오류(VALIDATION_ERROR) 등에서만 포함됩니다.
일부 성공 응답은 본문 없이 204 No Content입니다(로그아웃, 세션 폐기, API 키 삭제).
3. 엔드포인트 표
| Method | Path | Auth | 설명 |
|---|---|---|---|
POST |
/auth/register |
없음† | 회원가입 |
POST |
/auth/signup |
없음† | /auth/register와 동일 핸들러(별칭) |
POST |
/auth/login |
없음† | 로그인 · 세션 발급 |
POST |
/auth/logout |
Bearer | 현재 access 세션 폐기 → 204 |
POST |
/auth/refresh |
없음(본문 토큰) | access 토큰 회전 |
GET |
/auth/me |
Bearer | 현재 사용자 |
PATCH |
/auth/me |
Bearer | 프로필 부분 수정 |
GET |
/auth/sessions |
Bearer | 활성 세션 목록 |
DELETE |
/auth/sessions/{id} |
Bearer | 세션 하나 폐기 → 204 |
POST |
/auth/oauth/{provider} |
— | 501 NOT_IMPLEMENTED |
POST |
/captcha/challenge |
없음 | PoW 챌린지 발급 |
POST |
/captcha/verify |
없음 | 토큰 검증(서버·디버그) |
POST |
/developer/keys |
Bearer | API 키 발급 → 201 |
GET |
/developer/keys |
Bearer | 내 키 목록 |
DELETE |
/developer/keys/{id} |
Bearer | 키 폐기 → 204 |
† CAPTCHA_HMAC_SECRET이 설정된 환경에서는 register/login 본문에 유효한 zcaptcha_token이 필요합니다.
4. Captcha
캡차 프로토콜·환경 변수·풀이 흐름은 Captcha에 자세히 있습니다.
POST /captcha/challenge
- 503
CAPTCHA_NOT_CONFIGURED—CAPTCHA_HMAC_SECRET미설정 - 성공 시
data예:
{
"algorithm": "SHA-256",
"challenge": "…",
"salt": "…",
"signature": "…",
"maxnumber": 100000
}
POST /captcha/verify
요청:
{ "token": "<solved-token>", "dev_host": "localhost" }
- 503
CAPTCHA_NOT_CONFIGURED - 403
CAPTCHA_FAILED— 토큰 무효 - 성공:
{ "success": true }(봉투의data안)
register / login 게이트 (zcaptcha_token)
| 서버 설정 | 동작 |
|---|---|
CAPTCHA_HMAC_SECRET 없음 |
게이트 비활성(통과). 가입/로그인을 막지 않음 |
CAPTCHA_HMAC_SECRET 있음 |
본문 zcaptcha_token 필수. 실패 시 403 CAPTCHA_FAILED |
CAPTCHA_DEV_BYPASS=1 + 허용 토큰/호스트 |
개발 우회 가능 |
5. Register / Signup
POST /auth/register ≡ POST /auth/signup
요청 본문
| 필드 | 필수 | 규칙 |
|---|---|---|
email |
✅ | @ 포함 |
password |
✅ | 최소 8자 |
password_confirm |
✅ | password와 일치 |
handle |
✅ | 비어 있으면 안 됨 |
display_name |
선택 | 없으면 handle을 표시 이름으로 사용 |
zcaptcha_token |
조건부 | 시크릿 설정 시 필수 |
성공 201
data.user + data.tokens (access_token, refresh_token, token_type, expires_in)
주요 오류
| HTTP | code | 상황 |
|---|---|---|
| 409 | ALREADY_AUTHENTICATED |
유효한 Bearer로 이미 로그인한 채 가입 시도 |
| 409 | EMAIL_EXISTS |
이메일 중복 |
| 409 | LEGACY_ACCOUNT_EXISTS |
주전자닷컴 레거시에 동일 email/handle 존재 → 로그인으로 안내 |
| 409 | HANDLE_EXISTS |
handle 중복(또는 DB 중복) |
| 422 | VALIDATION_ERROR |
비밀번호·이메일·handle 검증 실패 |
| 403 | CAPTCHA_FAILED |
캡차 게이트 실패 |
6. Login
POST /auth/login
요청 본문
| 필드 | 필수 | 설명 |
|---|---|---|
identifier 또는 email |
✅ (둘 중 하나) | identifier 우선. 이메일·아이디·닉네임 |
password |
✅ | |
device_id |
선택 | 세션 메타 |
zcaptcha_token |
조건부 | 캡차 시크릿 설정 시 |
서버는 (1) zuku bcrypt 계정 → (2) 레거시 계정 연동 순으로 시도합니다. 레거시 성공 시 zuku 행으로 링크 후 bcrypt 재해시합니다.
성공 200
data.user + data.tokens
주요 오류
| HTTP | code |
|---|---|
| 401 | INVALID_CREDENTIALS |
| 403 | ACCOUNT_SUSPENDED |
| 403 | CAPTCHA_FAILED |
7. Logout / Refresh
POST /auth/logout
- 헤더:
Authorization: Bearer <access_token> - 성공: 204 (본문 없음)
- 헤더 없음/형식 오류: 401
UNAUTHORIZED
POST /auth/refresh
{ "refresh_token": "…" }
- 성공:
data.tokens(새access_token, 기존refresh_token유지,expires_in≈ 7일) - 실패: 401
INVALID_REFRESH_TOKEN
8. Me (프로필)
GET /auth/me
Bearer 필수. data.user 반환.
공개 사용자 필드 예: id(usr_…), email, display_name, handle, role, avatar_url, is_verified, created_at, profile_completion_status, bio, website_url, location, cover_url, banner_tone
PATCH /auth/me
전달된 필드만 갱신. 허용 필드: display_name, bio, website_url, location, banner_tone, cover_url, avatar_url
banner_tone은 cyan | swipe | jump | slate 중 하나.
9. Sessions
중복 로그인 시 강제 전체 로그아웃 대신, 사용자가 낯선 세션만 끊을 수 있습니다.
GET /auth/sessions
data.sessions[]: id, device_label, created_at, last_seen_at, is_current
토큰 원문은 절대 내려주지 않습니다.
DELETE /auth/sessions/{id}
{id}는 숫자 세션 id- 본인 소유만 폐기. 없거나 타인 → 404
- 성공: 204
10. OAuth
POST /auth/oauth/{provider}
항상 501 Not Implemented, code NOT_IMPLEMENTED.
스테이징에서 provider client secret 미발급으로 스코프 아웃된 상태입니다.
11. Developer API Keys
모두 Bearer 필수.
POST /developer/keys
{ "name": "CI bot" }
name: 1~50자- 201:
data.api_key(메타) +data.key(평문, 한 번만)
GET /developer/keys
data.api_keys 배열
DELETE /developer/keys/{id}
성공 204. 없거나 타인 → 404 API_KEY_NOT_FOUND
발급 키는 X-API-Key: sk_live_… 형태로 일부 API에서 Bearer 대신 사용할 수 있습니다.
12. curl · JS/TS 예제
아래 예는 로컬 Base를 사용합니다. 프로덕션은 https://zuzunza.com/api/v1로 바꾸면 됩니다.
Register
curl -sS -X POST "http://localhost:3001/api/v1/auth/register" \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"password": "password1",
"password_confirm": "password1",
"handle": "demo_user",
"zcaptcha_token": "OPTIONAL_WHEN_CAPTCHA_SET"
}'
const base = "http://localhost:3001/api/v1";
const registerRes = await fetch(`${base}/auth/register`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
email: "[email protected]",
password: "password1",
password_confirm: "password1",
handle: "demo_user",
// CAPTCHA_HMAC_SECRET 설정 시:
// zcaptcha_token: solvedToken,
}),
});
const registerJson = await registerRes.json();
const { access_token, refresh_token } = registerJson.data.tokens;
Login
curl -sS -X POST "http://localhost:3001/api/v1/auth/login" \
-H "Content-Type: application/json" \
-d '{
"identifier": "[email protected]",
"password": "password1"
}'
const loginRes = await fetch(`${base}/auth/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
identifier: "[email protected]",
password: "password1",
}),
});
const loginJson = await loginRes.json();
const accessToken = loginJson.data.tokens.access_token;
const refreshToken = loginJson.data.tokens.refresh_token;
Refresh
curl -sS -X POST "http://localhost:3001/api/v1/auth/refresh" \
-H "Content-Type: application/json" \
-d "{\"refresh_token\":\"$REFRESH\"}"
const refreshRes = await fetch(`${base}/auth/refresh`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ refresh_token: refreshToken }),
});
const refreshed = await refreshRes.json();
const newAccess = refreshed.data.tokens.access_token;
Me
curl -sS "http://localhost:3001/api/v1/auth/me" \
-H "Authorization: Bearer $ACCESS"
const meRes = await fetch(`${base}/auth/me`, {
headers: { Authorization: `Bearer ${accessToken}` },
});
const me = await meRes.json();
console.log(me.data.user.handle);
Create API key
curl -sS -X POST "http://localhost:3001/api/v1/developer/keys" \
-H "Authorization: Bearer $ACCESS" \
-H "Content-Type: application/json" \
-d '{"name":"my-integration"}'
const keyRes = await fetch(`${base}/developer/keys`, {
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ name: "my-integration" }),
});
const keyJson = await keyRes.json();
// 평문 키는 이 응답에서만 보관할 것
const rawKey = keyJson.data.key;
13. 오류 코드 요약
| code | 전형적 HTTP | 의미 |
|---|---|---|
ALREADY_AUTHENTICATED |
409 | 로그인 상태에서 가입 |
EMAIL_EXISTS |
409 | 이메일 중복 |
LEGACY_ACCOUNT_EXISTS |
409 | 레거시 계정 존재 → 로그인 |
HANDLE_EXISTS |
409 | handle 중복 |
VALIDATION_ERROR |
422 | 필드 검증 |
INVALID_CREDENTIALS |
401 | 로그인 실패 |
ACCOUNT_SUSPENDED |
403 | 정지 계정 |
UNAUTHORIZED |
401 | Bearer 없음/무효 |
INVALID_REFRESH_TOKEN |
401 | refresh 무효 |
CAPTCHA_NOT_CONFIGURED |
503 | 캡차 시크릿 없음(challenge/verify) |
CAPTCHA_FAILED |
403 | 캡차 실패 |
NOT_IMPLEMENTED |
501 | OAuth |
API_KEY_NOT_FOUND |
404 | 키 삭제 대상 없음 |
NOT_FOUND |
404 | 세션 등 |
*ZUKU API · Authentication · router.rs 기준*