0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Qiita API v2 に画像アップロードはない。エディタの内部APIを読んで実測した

0
Posted at

Qiita API v2 には公式の画像アップロードがありません。Web エディタが内部で何を叩いているかを bundle から読み解き、実際に叩いて仕様を測りました。非公式 API なので将来変わる可能性はあります。

Qiita の記事にプログラムから画像を貼りたい——でも POST /api/v2/items はテキストしか受け付けません。

エディタの JS bundle(v3-editor-bundle) を読んだら、画像アップロードは 「ポリシー発行 → S3 直接 POST」 の2段構成でした。Qiita のサーバは画像を一切中継しません。

検証: 2026-09-21 / Qiita Web エディタと同一エンドポイント / Chrome セッション Cookie + CSRF トークンで実行


全体シーケンス: Qiita は署名だけ発行する

アップロードのシーケンス

  1. POST /api/upload/policies — サイズ・MIME・ファイル名を宣言すると S3 の presigned POST 情報が返る
  2. 返ってきた form フィールド全部 + fileupload_url (= qiita-image-store.s3.amazonaws.com) に multipart POST
  3. 204 が返れば完了。asset.href がそのまま記事に貼れる公開 URL

Qiita 側で画像を保存しているわけではなく、発行した署名を使ってクライアントが直接 S3 に置きに行く設計です。レスポンスの form は S3 presigned POST の定型フィールドでした。

{
  "upload_url": "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com",
  "form": {
    "key": "0/<user_id>/<uuid>.png",
    "acl": "public-read",
    "Content-Type": "image/png",
    "policy": "eyJleHBpcmF0aW9uIjoi...",
    "x-amz-credential": "...",
    "x-amz-algorithm": "AWS4-HMAC-SHA256",
    "x-amz-date": "...",
    "x-amz-signature": "..."
  },
  "asset": {
    "href": "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/<user_id>/<uuid>.png",
    "name": "a.png",
    "id": 5011669,
    "size": 100
  }
}

バリデーションを叩いて測った

許可される content_type・サイズ上限・必須項目を実際に変えて投げてみました。

バリデーション結果

条件 結果 レスポンス
png / jpeg / gif / avif ✅ 200 policy 発行
webp / svg / mp4 / pdf ❌ 422 content_type は一覧にありません
size > 10MB ❌ 413 size must be less than 10 MB
name 欠落 ❌ 422 name を入力してください
content_type 欠落 ❌ 422 content_type を入力してください

意外なのは webp が拒否で avif が通る点です。モダン形式の受け入れ順が一般的な印象と逆なので、webp を上げるスクリプトは素直に png に変換してからにした方が安全です。


実装: 30行で完結するアップローダ

セッション Cookie と CSRF トークンがあれば、ブラウザを介さず fetch だけで完結します。

async function uploadToQiita(imagePath: string, cookie: string, csrf: string) {
  const buf = fs.readFileSync(imagePath);

  // 1. ポリシー発行
  const policy = await fetch('https://qiita.com/api/upload/policies', {
    method: 'POST',
    headers: { cookie, 'x-csrf-token': csrf, 'content-type': 'application/json' },
    body: JSON.stringify({
      image: { size: buf.length, content_type: 'image/png', name: 'image.png' },
    }),
  }).then((r) => r.json());

  // 2. S3 へ直接 POST
  const fd = new FormData();
  for (const k in policy.form) fd.append(k, policy.form[k]);
  fd.append('file', new Blob([buf], { type: 'image/png' }), 'image.png');
  await fetch(policy.upload_url, { method: 'POST', body: fd }); // 204

  return policy.asset.href; // → ![image.png](href) で貼れる
}

返ってきた href をそのまま Markdown の ![](…) に入れれば、記事内に Qiita ホストの画像として表示されます。本記事の図もすべてこの方法で置いています。


まとめ

  • Qiita の画像アップロードは /api/upload/policies → S3 presigned POST の2段構成。公式 API ではないが構造は単純
  • 許可形式は png / jpeg / gif / avif。webp・svg は 422。上限は 10MB
  • 画像は qiita-image-store バケットに 0/<user_id>/<uuid>.<ext> で置かれ、acl: public-read で即公開される

非公式エンドポイントのため仕様変更リスクがあります。記事投稿本体は引き続き安定した Qiita API v2 を使い、画像だけこの経路に逃がす分業が現実的です。

Qiita への自動投稿・CLI クライアントを作るときの参考になれば幸いです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?