5ch専用ブラウザがすぐ実装できるよう最小限の仕様にしています。画像アップロードは認証不要。一方で、ユーザーアカウント機能(ログイン、プロフィール、パスワード変更等)を使う場合は、Cookieベースのセッション+CSRFを利用します。
https://gzo.aiユーザーアカウントを使うクライアント向けの最小仕様です。セッションはCookie(HttpOnly, SameSite=Lax)で管理し、状態変更系はCSRFトークンを必須とします。CORSはOrigin反射で許可されます。
gzo_session(HttpOnly / Path=/ / SameSite=Lax / Secure=環境依存)。ログイン成功時にSet-Cookieで付与。GET /api/csrf で gzo_csrf Cookieを受け取り、以後の POST で X-CSRF-Token ヘッダに同値をエコー。credentials: 'include' を必ず指定。Content-Type は JSON または form-urlencoded をサポート。成功すると仮ログイン(セッション発行)し、確認メールを送信します。
{
"email": "[email protected]",
"password": "<12+ chars>",
"username": "optional_lowercase_3-20"
}成功するとセッションCookieが発行され、レスポンスはユーザー情報+メール未確認フラグを返します。
{
"user": {"id": "u_xxx", "email": "[email protected]", "username": null},
"needsVerification": true
}await fetch("https://gzo.ai/api/auth/login",{
method:"POST",
credentials:"include",
headers:{"Content-Type":"application/json"},
body:JSON.stringify({email, password})
});curl -i -c cookies.txt -b cookies.txt \
-H 'Content-Type: application/json' \
-d '{"email":"[email protected]","password":"hunter2"}' \
https://gzo.ai/api/auth/loginセッションCookie同伴で現在ログイン中のユーザーを返します。未ログインは
user: null。curl -b cookies.txt https://gzo.ai/api/auth/me
# → {"user": {"id":"u_...","email":"...","username":null}}CSRF必須版(POST)と簡易版(GET)の両方があります。ブラウザからは
credentials: 'include' を忘れずに。GET /api/csrf を呼び、gzo_csrf Cookieを取得。POST /api/auth/logout, POST /api/auth/me, POST /api/password/change, POST /api/password/reset/start, POST /api/password/reset/complete, POST /api/auth/verify/resend などの状態変更系に X-CSRF-Token: <gzo_csrf cookie値> を必ず付与。// 1) CSRF発行
await fetch("https://gzo.ai/api/csrf", {credentials:"include"});
// 2) Cookie gzo_csrf を読み出して、以後のPOSTでX-CSRF-Tokenにセット
const csrf = document.cookie.split('; ').find(x=>x.startsWith('gzo_csrf='))?.split('=')[1];
await fetch("https://gzo.ai/api/password/change",{
method:"POST",
credentials:"include",
headers:{"Content-Type":"application/json","X-CSRF-Token":csrf},
body:JSON.stringify({current:"oldpass", new:"newlongpassword"})
});メール内リンクから遷移。成功時は
ok:true とユーザー情報を返し、そのまま自動ログイン(セッションCookie発行)。スパム防止でIP/メール別にスロットルされます。
/start にメールを送る → リセット用メール送付(常に 200)。/complete に {token,newPassword} を送信 → 成功で全セッション失効。0-9a-zA-Z)。長さはサーバ側で決定(既定7、許容5〜16)。POST /3/image— 画像アップロード(multipart)。GET /i/:id— 元画像(拡張子なし)。GET /i/:id.:ext— 拡張子を明示(jpg,png,gif,webpのいずれか)。POST /3/image/:id/delete?token=<tok>— 削除(アップロード時に発行されたトークンが必要)。POST /3/imagemultipart/form-dataの単一ファイル。1…86400(超過値は86400に丸め)。{
"success": true,
"status": 200,
"data": {
"id": "a1B9zQ7",
"link": "https://gzo.ai/i/a1B9zQ7",
"extension": "jpg",
"mime": "image/jpeg",
"bytes": 123456,
"claimed_content_type": "image/jpeg",
"ttl_seconds": 86400,
"expires_at": 1724966400,
"delete_url": "https://gzo.ai/3/image/a1B9zQ7/delete?token=<opaque-token>",
"thumbs": {
"s": "https://gzo.ai/i/a1B9zQ7s",
"b": "https://gzo.ai/i/a1B9zQ7b",
"t": "https://gzo.ai/i/a1B9zQ7t",
"m": "https://gzo.ai/i/a1B9zQ7m",
"l": "https://gzo.ai/i/a1B9zQ7l",
"h": "https://gzo.ai/i/a1B9zQ7h"
}
}
}400—image欠如 / 非対応形式 / 保存失敗 / 不正な入力403— 禁止(IPブロック)413— ファイルサイズ上限超過429— リクエスト過多(レート制限)削除URL: 成功レスポンスのdelete_urlに、1回使い切りのトークン付き削除エンドポイントを返します。
注: 古いアップロードには削除トークンが無い場合があります(その場合はTTL削除のみ)。
補足: EXIFは除去され、可能な場合は自動回転されます。サムネ生成に失敗したサイズはthumbsに含まれないことがあります。
# 基本
curl -F "image=@/path/to/pic.jpg" https://gzo.ai/3/image
# TTLを10分に指定
curl -F "image=@/path/to/pic.jpg" -F "ttl=600" https://gzo.ai/3/imageGET /i/:id→200 image/*または404
GET /i/a1B9zQ7 → 200 image/jpegGET /i/:id.:ext(:ext ∈ {jpg,png,gif,webp})
GET /i/a1B9zQ7.jpg → 200 image/jpegthumbsは「1文字サフィックス → URL」のマップです。
| サフィックス | 種別 | 目標サイズ(px) | 動作 | URL例 |
|---|---|---|---|---|
s | 正方形 | 90 × 90 | 中央トリミング後に縮小 | /i/<id>s |
b | 正方形 | 160 × 160 | 中央トリミング後に縮小 | /i/<id>b |
t | フィット | 240 × 240 | 長辺基準で収める(拡大しない) | /i/<id>t |
m | フィット | 320 × 320 | 長辺基準で収める(拡大しない) | /i/<id>m |
l | フィット | 640 × 640 | 長辺基準で収める(拡大しない) | /i/<id>l |
h | フィット | 1024 × 1024 | 長辺基準で収める(拡大しない) | /i/<id>h |
実装メモ: サムネイルも/i/で配信され、IDにサフィックス(例:<id>s)を付けたものになります。
POST /3/image/:id/deleteアップロード成功時に返されるdelete_url(/3/image/:id/delete?token=...)を叩くと、その画像とサムネイル、メタ情報が削除対象になります。
POST /3/image/a1B9zQ7/delete?token=AbCd...{
"success": true,
"status": 200,
"deleted": true,
"id": "a1B9zQ7",
"marked": true
}markedは内部メタの「削除済み」フラグ更新に成功したかを示します。
400—token不足などの不正入力。403— 無効なトークン、またはトークン未対応の古いアップロード。404— 該当IDが存在しない。# 返ってきた delete_url をそのまま叩く(推奨)
curl -X POST "https://gzo.ai/3/image/a1B9zQ7/delete?token=AbCdEf..."
# または token をフォームで渡す(同等)
curl -X POST -F "token=AbCdEf..." "https://gzo.ai/3/image/a1B9zQ7/delete"POST /3/imageに対して、IPごとに適用。X-RateLimit-Limit: <int>
X-RateLimit-Remaining: <int>
X-RateLimit-Window: <seconds>429の場合は次も返ります:
Retry-After: <seconds>{
"success": false,
"status": 400,
"error": "human-readable message",
"...optional context...": "e.g. max_bytes, ip"
}よくあるメッセージ(英語表記のまま):"missing 'image' field (multipart/form-data)"、"unsupported or unrecognized image format (jpeg/png/gif/webp)"、"file too large"、"forbidden (banned IP)"、"not found"、"too many requests"。
削除関連の例:"missing token"、"invalid token"、"delete not available for this upload"。
image(1リクエスト1ファイル)。data.link(元画像)またはdata.thumbs[k]を使う。s/b、通常 →t/m、大きめ →l/h。data.expires_at(UNIX秒)で失効。404になったら表示を抑制するなどの処理を推奨。delete_urlを安全に保管し、ユーザが明示操作した時のみPOSTを実行。429とRetry-Afterをユーザに分かる形で表示・再試行制御。resp = POST multipart /3/image
fields:
image = <file bytes>
ttl = <任意・1..86400秒>
if resp.status in 200..299 and resp.json.success:
id = resp.json.data.id
link = resp.json.data.link # 元画像
thumbs = resp.json.data.thumbs # サフィックス -> URL
表示は link か、希望サイズの thumbs を使用
else:
表示: resp.json.error
status==429 かつ "Retry-After" ヘッダがあれば、待って再試行# 成功
curl -s -F 'image=@/tmp/a.jpg' https://gzo.ai/3/image | jq .
# サイズ超過
curl -s -F 'image=@/tmp/big.png' https://gzo.ai/3/image | jq .
# フィールド名ミス(失敗例)
curl -s -F 'file=@/tmp/a.jpg' https://gzo.ai/3/image | jq .
# 元画像(拡張子なし)取得
curl -I https://gzo.ai/i/a1B9zQ7
# サムネイル取得
curl -I https://gzo.ai/i/a1B9zQ7m© Synic Inc.