ZUKU API — Examples
인증·캡차·피드·콘텐츠·소셜을 잇는 실전 시나리오 모음입니다.
각 예제는 backend/rs/src/router.rs 경로를 기준으로 하며, Base는 프로덕션 https://zuzunza.com/api/v1입니다.
목차
- 공통 헬퍼
- 가입 → 로그인 → me
- 캡차 포함 가입
- 공개 피드 조회
- 콘텐츠 생성 · 업로드
- 좋아요 · 북마크 · 팔로우
- 댓글 · 알림 · DM
- Game Cloud (온라인 게임)
- 에러 처리 패턴
1. 공통 헬퍼
const API = "https://zuzunza.com/api/v1";
type Envelope<T> =
| { success: true; data: T; meta: { request_id: string; timestamp: string; version: string } }
| {
success: false;
error: { code: string; message: string; details?: { field: string; message: string }[] };
meta: { request_id: string; timestamp: string; version: string };
};
async function api<T>(
path: string,
init: RequestInit & { token?: string; apiKey?: string } = {},
): Promise<T> {
const headers = new Headers(init.headers);
headers.set("Accept-Language", "ko-KR");
headers.set("X-Request-Id", crypto.randomUUID());
if (init.body && !headers.has("Content-Type") && !(init.body instanceof FormData)) {
headers.set("Content-Type", "application/json");
}
if (init.token) headers.set("Authorization", `Bearer ${init.token}`);
if (init.apiKey) headers.set("X-API-Key", init.apiKey);
const res = await fetch(`${API}${path}`, { ...init, headers });
if (res.status === 204) return undefined as T;
const env = (await res.json()) as Envelope<T>;
if (!env.success) {
const err = new Error(`${env.error.code}: ${env.error.message}`);
(err as Error & { code?: string }).code = env.error.code;
throw err;
}
return env.data;
}
2. 가입 → 로그인 → me
curl
# 로그인 (캡차 게이트 비활성 환경)
curl -sS -X POST "$API/auth/login" \
-H "Content-Type: application/json" \
-d '{"identifier":"demo_user","password":"SecureP@ss123"}'
# me
curl -sS "$API/auth/me" -H "Authorization: Bearer $ACCESS"
JS/TS
const tokens = await api<{
tokens: { access_token: string; refresh_token: string; expires_in: number };
}>("/auth/login", {
method: "POST",
body: JSON.stringify({ identifier: "demo_user", password: "SecureP@ss123" }),
});
const me = await api<{ user: { id: string; handle: string } }>("/auth/me", {
token: tokens.tokens.access_token,
});
상세 필드: Authentication
3. 캡차 포함 가입
시크릿이 켜진 환경에서는 zcaptcha_token이 필요합니다.
const challenge = await api<{
algorithm: string;
challenge: string;
salt: string;
signature: string;
maxnumber: number;
}>("/captcha/challenge", { method: "POST" });
// PoW 풀이 → base64 JSON 토큰 (프로토콜은 Captcha 문서 참고)
const zcaptcha_token = await solvePowInWorker(challenge);
await api("/auth/register", {
method: "POST",
body: JSON.stringify({
email: "[email protected]",
password: "SecureP@ss123",
password_confirm: "SecureP@ss123",
handle: "newcreator",
zcaptcha_token,
}),
});
전체 계약: Captcha
4. 공개 피드 조회
curl -sS "$API/feeds/hype?page=1&per_page=20"
curl -sS "$API/feeds?category=swipe&page=1&per_page=12&sort=hot"
curl -sS "$API/feed?limit=20" # 커뮤니티 타임라인 (cursor: before)
const feed = await api<{ items: unknown[]; pagination: unknown }>(
"/feeds/hype?page=1&per_page=8",
);
상세: Feeds
5. 콘텐츠 생성 · 업로드
업로드 (multipart)
curl -sS -X POST "$API/uploads" \
-H "Authorization: Bearer $ACCESS" \
-F "file=@./clip.mp4"
const fd = new FormData();
fd.append("file", fileBlob, "clip.mp4");
const uploaded = await api<{ url: string; mime: string; size: number }>(
"/uploads",
{ method: "POST", body: fd, token: access },
);
콘텐츠 생성 (Bearer 또는 API 키)
curl -sS -X POST "$API/contents" \
-H "Authorization: Bearer $ACCESS" \
-H "Content-Type: application/json" \
-d '{"category":"hype","type":"video","title":"첫 작품","media_url":"'"$URL"'"}'
6. 좋아요 · 북마크 · 팔로우
await api(`/contents/${id}/like`, { method: "POST", token: access });
await api(`/contents/${id}/bookmark`, { method: "POST", token: access });
// 크리에이터 팔로우 토글 (handle)
const follow = await api<{ is_following: boolean; follower_count: number }>(
`/creators/${handle}/follow`,
{ method: "POST", token: access },
);
소셜 전반: Social
7. 댓글 · 알림 · DM
await api(`/contents/${id}/comments`, {
method: "POST",
token: access,
body: JSON.stringify({ body: "좋은 작품이에요!" }),
});
const notifs = await api<{ notifications: unknown[] }>("/notifications", {
token: access,
});
const dm = await api<{ conversation: { id: string } }>("/dm/conversations", {
method: "POST",
token: access,
body: JSON.stringify({ handle: "friend_handle" }),
});
8. Game Cloud (온라인 게임)
Studio → Game Cloud에서 projectId를 만든 뒤:
const PROJECT = "YOUR_PROJECT_UUID";
// 지갑
const wallet = await api<{ POINT: number; CASH_KRW: number }>(
`/cloud/economy/balance?projectId=${PROJECT}`,
{ token: access },
);
// 변수 (최고 점수)
await api("/cloud/vars/mutate", {
method: "POST",
token: access,
body: JSON.stringify({
projectId: PROJECT,
scope: "user",
key: "high_score",
op: "max",
num: 9999,
}),
});
// 세이브
await api("/cloud/saves/save", {
method: "POST",
token: access,
body: JSON.stringify({
projectId: PROJECT,
slot: "default",
data: { level: 3, inventory: ["sword"] },
}),
});
샘플: samples/cloud-rpg/ · 상세: Game Cloud
9. 에러 처리 패턴
try {
await api("/auth/login", {
method: "POST",
body: JSON.stringify({ identifier, password }),
});
} catch (e) {
const code = (e as Error & { code?: string }).code;
if (code === "CAPTCHA_FAILED") {
// 새 challenge 후 재시도
} else if (code === "UNAUTHORIZED") {
// refresh 또는 재로그인
} else if (code === "RATE_LIMITED") {
// X-RateLimit-Reset 대기
}
throw e;
}
코드 표: Errors
다음 단계
| 문서 | 용도 |
|---|---|
| Getting Started | 봉투·Rate Limit·Versioning |
| Captcha | PoW 세부 |
| Authentication | 세션·API 키 |
| 사이트 | /docs/api/examples |