gzo.ai — シンプル画像ホスト API(5ch専用ブラウザ向け)

5ch専用ブラウザがすぐ実装できるよう最小限の仕様にしています。画像アップロードは認証不要。一方で、ユーザーアカウント機能(ログイン、プロフィール、パスワード変更等)を使う場合は、Cookieベースのセッション+CSRFを利用します。

基本

Base URL
本番:https://gzo.ai
画像アップロード/取得は不要。アカウント機能はCookieセッションを使用。
なし
リクエスト
アップロード時はmultipart/form-data
レスポンス
APIはJSON(UTF-8)。画像取得はバイナリ。

アカウント/認証(任意機能)

ユーザーアカウントを使うクライアント向けの最小仕様です。セッションはCookie(HttpOnly, SameSite=Lax)で管理し、状態変更系はCSRFトークンを必須とします。CORSはOrigin反射で許可されます。

ログイン/登録

登録(任意・メール確認あり) — POST /api/auth/register

Content-Type は JSON または form-urlencoded をサポート。成功すると仮ログイン(セッション発行)し、確認メールを送信します。

{
  "email": "[email protected]",
  "password": "<12+ chars>",
  "username": "optional_lowercase_3-20"
}

ログイン — POST /api/auth/login

成功するとセッションCookieが発行され、レスポンスはユーザー情報+メール未確認フラグを返します。

{
  "user": {"id": "u_xxx", "email": "[email protected]", "username": null},
  "needsVerification": true
}

フロント実装例(fetch)

await fetch("https://gzo.ai/api/auth/login",{
  method:"POST",
  credentials:"include",
  headers:{"Content-Type":"application/json"},
  body:JSON.stringify({email, password})
});

curl例

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

現在ユーザー — GET /api/auth/me

セッションCookie同伴で現在ログイン中のユーザーを返します。未ログインは

user: null
curl -b cookies.txt https://gzo.ai/api/auth/me
# → {"user": {"id":"u_...","email":"...","username":null}}

ログアウト — POST/GET /api/auth/logout

CSRF必須版(POST)と簡易版(GET)の両方があります。ブラウザからは

credentials: 'include' を忘れずに。

CSRFの扱い(重要)

JS例

// 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"})
});

メール確認/パスワード再設定

メール確認完了 — GET/POST /api/auth/verify(*/complete)?token=...

メール内リンクから遷移。成功時は

ok:true とユーザー情報を返し、そのまま自動ログイン(セッションCookie発行)。

確認メール再送 — POST /api/auth/verify/resend(CSRF必須・要ログイン)

スパム防止でIP/メール別にスロットルされます。

パスワード再設定 — POST /api/password/reset/start & /complete(CSRF必須)

IDとファイル

エンドポイント

アップロード —POST /3/image

フォームフィールド

成功(200)

{
  "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"
    }
  }
}

エラー

削除URL: 成功レスポンスのdelete_urlに、1回使い切りのトークン付き削除エンドポイントを返します。

注: 古いアップロードには削除トークンが無い場合があります(その場合はTTL削除のみ)。

補足: EXIFは除去され、可能な場合は自動回転されます。サムネ生成に失敗したサイズはthumbsに含まれないことがあります。

curl例

# 基本
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/image

取得

元画像(拡張子なし)

GET /i/:id200 image/*または404

GET /i/a1B9zQ7  → 200 image/jpeg

拡張子を明示

GET /i/:id.:ext:ext ∈ {jpg,png,gif,webp}

GET /i/a1B9zQ7.jpg  → 200 image/jpeg

サムネイル

thumbsは「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=...)を叩くと、その画像とサムネイル、メタ情報が削除対象になります。

パラメータ

成功(200)

{
  "success": true,
  "status": 200,
  "deleted": true,
  "id": "a1B9zQ7",
  "marked": true
}

markedは内部メタの「削除済み」フラグ更新に成功したかを示します。

エラー

curl例

# 返ってきた 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"

注意点

レート制限

X-RateLimit-Limit: <int>
X-RateLimit-Remaining: <int>
X-RateLimit-Window: <seconds>

429の場合は次も返ります:

Retry-After: <seconds>

エラー形式(JSON)

{
  "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"

クライアント実装指針(5chブラウザ向け)

最小疑似コード(アップロード)

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)

# 成功
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.