Examples#
A collection of real-world scenarios that connect authentication, captcha, feeds, content, and social features.
All examples call the HTTP API directly with curl or fetch; no npm SDK is required. The base URL is production, https://zuzunza.com/api/v1. In the curl examples, set it first:
API="https://zuzunza.com/api/v1"
Contents#
- Shared helper
- Sign up → log in → me
- Sign up with captcha
- Read public feeds
- Create content and upload
- Likes, bookmarks, follows
- Comments, notifications, DMs
- Game Cloud (online games)
- Error handling pattern
1. Shared helper#
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;
}
The helper returns the envelope's data.
2. Sign up → log in → me#
curl#
# Log in (environment with the captcha gate disabled)
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,
});
Field details: Authentication
3. Sign up with captcha#
When the captcha secret is enabled, zcaptcha_token is required.
const challenge = await api<{
algorithm: string;
challenge: string;
salt: string;
signature: string;
maxnumber: number;
}>("/captcha/challenge", { method: "POST" });
// Solve the PoW → base64 JSON token (see the Captcha page for the protocol)
const zcaptcha_token = await solvePowInWorker(challenge);
await api("/auth/register", {
method: "POST",
body: JSON.stringify({
email: "new@example.com",
password: "SecureP@ss123",
password_confirm: "SecureP@ss123",
handle: "newcreator",
zcaptcha_token,
}),
});
solvePowInWorker is your own Web Worker solver. Full contract: Captcha
4. Read public feeds#
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" # Community timeline (cursor: before)
const feed = await api<{ items: unknown[]; pagination: unknown }>(
"/feeds/hype?page=1&per_page=8",
);
Note: media /feeds routes use a fixed server sort and ignore sort, and the list is returned in data.feeds (with data.pagination). Details: Feeds
5. Create content and upload#
Upload (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 },
);
Note: the upload response wraps the file metadata in data.upload, so read the URL from the upload object (see Media).
Create content (Bearer or API key)#
curl -sS -X POST "$API/contents" \
-H "Authorization: Bearer $ACCESS" \
-H "Content-Type: application/json" \
-d '{"category":"hype","type":"video","title":"My first work","media_url":"'"$URL"'"}'
Note: type must match the category. For hype, use one of interactive_longform, horizontal_media, or photo_media; otherwise the API returns 422 VALIDATION_ERROR.
6. Likes, bookmarks, follows#
await api(`/contents/${id}/like`, { method: "POST", token: access });
await api(`/contents/${id}/bookmark`, { method: "POST", token: access });
// Toggle following a creator (by handle)
const follow = await api<{ is_following: boolean; follower_count: number }>(
`/creators/${handle}/follow`,
{ method: "POST", token: access },
);
Social overview: Social
7. Comments, notifications, DMs#
await api(`/contents/${id}/comments`, {
method: "POST",
token: access,
body: JSON.stringify({ body: "Great work!" }),
});
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 (online games)#
After creating a projectId in Studio → Game Cloud:
const PROJECT = "YOUR_PROJECT_UUID";
// Wallet
const wallet = await api<{ POINT: number; CASH_KRW: number }>(
`/cloud/economy/balance?projectId=${PROJECT}`,
{ token: access },
);
// Variable (high score)
await api("/cloud/vars/mutate", {
method: "POST",
token: access,
body: JSON.stringify({
projectId: PROJECT,
scope: "user",
key: "high_score",
op: "max",
num: 9999,
}),
});
// Save
await api("/cloud/saves/save", {
method: "POST",
token: access,
body: JSON.stringify({
projectId: PROJECT,
slot: "default",
data: { level: 3, inventory: ["sword"] },
}),
});
Details: Game Cloud
9. Error handling pattern#
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") {
// Fetch a new challenge and retry
} else if (code === "UNAUTHORIZED") {
// Refresh the token or log in again
} else if (code === "RATE_LIMITED") {
// Wait until X-RateLimit-Reset
}
throw e;
}
Code table: Errors · Limits and retries: Rate limits
Next steps#
| Page | Use |
|---|---|
| Getting Started | Start here |
| Captcha | PoW details |
| Authentication | Sessions and API keys |
| Changelog | What changed and when |