Qiita API経由で記事を投稿すると、Web画面の下書き保存とは異なりデフォルトで即時「全体公開」されます。下書き(限定共有)状態で投稿するためには private パラメータの明示的な指定が不可欠です。
- Qiita API (
POST /api/v2/items) で投稿した記事は、デフォルト(未指定時)で全体公開(private: false)となる - 下書き相当の限定共有記事として投稿したい場合は、リクエストボディに
"private": trueを明示的に含める必要がある - 投稿に必要な必須項目は
title,body,tagsの3つであり、アクセストークンにはwrite_qiitaスコープが必要となる
Qiita APIでの投稿で即公開を避ける仕組みと「private」フラグの動作
QiitaのWebUI(ブラウザ上のエディタ)では、「下書き保存」ボタンを押すことで非公開のまま下書きを保存できます。しかし、Qiita API v2の POST /api/v2/items エンドポイントを用いて記事を作成する場合、WebUIのような「下書き状態」という独立した概念は存在せず、すべて「限定共有記事(private)」か「公開記事(public)」のいずれかとして扱われます。
APIリクエスト時に private パラメータを指定しなかった場合、デフォルト値は false と判定されます。この場合、リクエストが成功した瞬間に記事は全体公開(パブリック)となり、タイムラインや検索インデックスに反映されます。
検証目的のテスト投稿や、未完成の原稿をプログラム経由でアップロードする場合に private を指定し忘れると、意図しないタイミングで誤って全体公開されてしまうリスクがあります。
privateパラメータの取り得る値と記事の状態
private パラメータはブーリアン値(true または false)で指定します。
| 設定値 | 記事の状態 | 動作・アクセス範囲 |
|---|---|---|
"private": true |
限定共有記事 | 記事一覧や検索結果には表示されず、発行された固有URLを知っているユーザーのみが閲覧可能です。WebUIでの「限定共有」と同等です。 |
"private": false |
全体公開記事 | Qiita全体に公開され、タイムラインや検索結果、ユーザーの投稿一覧に即座に掲載されます。 |
| (未指定) | 全体公開記事 | パラメータのデフォルト値が false のため、自動的に全体公開として処理されます。 |
記事を安全にAPI投稿するための基本原則は、投稿時は常に "private": true を明示的に指定して限定共有記事として作成し、内容を確認した後に必要に応じてUIまたはAPI(PATCH /api/v2/items/:item_id)から公開状態(false)へ変更する というプロセスを徹底することです。
POST /api/v2/items のリクエストパラメータと必須項目一覧
Qiita API v2で記事を投稿するエンドポイント POST /api/v2/items に渡すJSONリクエストボディのパラメータ仕様を解説します。
公式ドキュメント(JSON Schema)に基づくと、記事投稿時に必須となる項目は title, body, tags の3点です。その他のパラメータは任意(オプショナル)または特定の条件下(Qiita Team等)でのみ機能します。
リクエストパラメータ早見表
| パラメータ名 | 型 | 必須 | 説明 |
|---|---|---|---|
title |
string | 必須 | 記事のタイトル(例: "PythonによるQiita API自動化") |
body |
string | 必須 | 記事の本文。Markdown形式で記述します(例: "# はじめに\n本文です。") |
tags |
array | 必須 | 記事に紐付けるタグの配列。最低1つのタグオブジェクトが必要です(例: [{"name": "Python"}]) |
private |
boolean | 任意 | 限定共有記事にするかどうか。true で限定共有、false で全体公開。未指定時は false
|
coediting |
boolean | 任意 | Qiita Team専用。記事を共同推敲モードにするかどうか |
group_url_name |
string | 任意 |
Qiita Team専用。記事を所属させるグループの url_name を指定 |
tweet |
boolean | 任意 | 投稿時にTwitter(X)へ連携ツイートを送信するかどうか |
gist |
boolean | 任意 | 投稿時に本文内のコードブロックをGistへ投稿するかどうか |
tags パラメータの構造について
tags パラメータは単なる文字列の配列(["Python", "API"])ではなく、オブジェクトの配列として渡す必要があります。
"tags": [
{
"name": "Python",
"versions": ["3.10", "3.11"]
},
{
"name": "API"
}
]
各タグオブジェクトには name(必須)と versions(任意: 使用言語やフレームワークのバージョン情報の配列)を含めることができます。versions を指定しない場合は、{"name": "Python"} のように指定します。
限定共有(private: true)記事をPythonで投稿する実装例
実際に Python の requests ライブラリを使用して、記事を限定共有(private: true)として投稿するスクリプトの実装例を示します。
セキュリティ上のベストプラクティスに基づき、アクセストークンはソースコード内に直接書き込まず、環境変数 QIITA_TOKEN から取得します。
コード例1:限定共有記事の新規投稿
以下のコードはコピー&ペーストしてそのまま動かせる最小限の実装です。
import os
import sys
import requests
# 1. 環境変数からアクセストークンを取得
token = os.environ.get("QIITA_TOKEN")
if not token:
print("エラー: 環境変数 QIITA_TOKEN が設定されていません。", file=sys.stderr)
sys.exit(1)
# 2. エンドポイントとリクエストヘッダーの設定
url = "https://qiita.com/api/v2/items"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
# 3. リクエストボディの組み立て(private: True を明示指定)
payload = {
"title": "API経由でのテスト投稿記事",
"body": "# テスト投稿\n\nこの記事はQiita API経由で限定共有として投稿されたテスト記事です。",
"tags": [
{"name": "Python"},
{"name": "QiitaAPI"}
],
"private": True # PythonのTrueはJSON出力時に true に変換されます
}
# 4. POSTリクエストの送信
try:
response = requests.post(url, headers=headers, json=payload, timeout=10)
# ステータスコードの確認(作成成功時は 201 Created)
print(f"Status Code: {response.status_code}")
if response.status_code == 201:
data = response.json()
print("投稿成功!")
print(f"記事ID: {data['id']}")
print(f"記事URL: {data['url']}")
print(f"限定共有フラグ (private): {data['private']}")
else:
print(f"投稿失敗: {response.status_code}")
print(f"エラーレスポンス: {response.text}")
except requests.exceptions.RequestException as e:
print(f"通信エラーが発生しました: {e}", file=sys.stderr)
レスポンスデータの検証
リクエストが正常に処理されると、HTTPステータスコード 201 Created が返却され、レスポンスボディには作成された記事のJSONオブジェクトが含まれます。
返却されたJSON内の "private" フィールドが true になっていること、および発行された "url"(https://qiita.com/items/<記事ID>)から限定共有ページへアクセスできることを確認してください。
Qiita APIの認証アクセストークンと4つのスコープ仕様
Qiita APIを利用して記事の書き込み(投稿・編集・削除)を行うには、認証された個人用アクセストークン(Personal Access Token)またはOAuth 2.0フローによって取得したトークンが必要です。
リクエストの際には、HTTPヘッダーに以下のように指定します。
Authorization: Bearer YOUR_ACCESS_TOKEN
4つのスコープとその権限範囲
アクセストークンを生成する際、そのトークンに許可する操作の範囲(スコープ)を選択します。Qiita API v2で定義されているスコープは以下の4種類です。
| スコープ名 | 対象サービス | 許可される操作 |
|---|---|---|
read_qiita |
個人版Qiita | 個人アカウントに紐づくデータ(記事、ストック、ユーザー情報等)の読み取り |
write_qiita |
個人版Qiita | 個人アカウントでのデータの作成・更新・削除(記事投稿、いいね、ストック等) |
read_qiita_team |
Qiita Team | Qiita Team上のデータの読み取り |
write_qiita_team |
Qiita Team | Qiita Team上でのデータの作成・更新・削除 |
記事投稿API(POST /api/v2/items)を呼び出す場合、個人版Qiitaであれば write_qiita スコープが含まれているトークンで認証する必要があります。スコープが不足しているトークンを使用すると、認証エラーまたは権限不足エラーが返却されます。
レート制限の仕組みとエラーハンドリング(401 / 403 / 429)
Qiita APIを安全に運用するためには、APIの制限事項とエラー応答に対する適切なハンドリングを実装する必要があります。
APIのレート制限(Rate Limit)
Qiita API v2では、サービスの安定性を保つためにクライアントからのリクエスト数に制限を設けています。
- 認証済みリクエスト: 1時間あたり 1,000回 まで
- 未認証リクエスト: 1時間あたり 60回 まで(接続元IPアドレス単位でカウント)
現在の残りリクエスト回数や制限解除までの時間は、APIレスポンスのHTTPヘッダーから取得可能です。
-
Rate-Limit: 1時間あたりの上限リクエスト数 -
Rate-Remaining: 残り利用可能回数 -
Rate-Reset: レート制限がリセットされる時刻(UNIXタイムスタンプ)
主なエラーレスポンスとステータスコード
記事投稿やAPI呼び出し時に発生する代表的なHTTPステータスコードと原因は以下の通りです。
| ステータスコード | エラー種別 | 主な原因と対策 |
|---|---|---|
| 400 | Bad Request | リクエストボディのJSON形式不備、必須項目(title, body, tags)の不足。 |
| 401 | Unauthorized |
Authorization ヘッダーが指定されていない、またはアクセストークンが無効・期限切れ。 |
| 403 | Forbidden | トークンのスコープ不足(write_qiita が付与されていない)、または操作権限がないリソースへのアクセス。 |
| 404 | Not Found | 存在しないエンドポイントや、指定した記事ID・ユーザーIDが見つからない。 |
| 429 | Too Many Requests | レート制限(1時間あたり1,000回)を超与した場合。Rate-Reset の時刻までリクエストを控える必要があります。 |
| 500 | Internal Server Error | Qiita側のサーバーエラー。時間を置いて再試行します。 |
コード例2:エラーハンドリングとレート制限確認を組み込んだ堅牢な投稿スクリプト
以下のスクリプトでは、投稿リクエストに対する詳細なステータスコード分岐、エラーメッセージの解析、およびレート制限ヘッダーの取得を行っています。
import os
import sys
import requests
def post_qiita_item_safe(title: str, body: str, tags: list, private: bool = True):
token = os.environ.get("QIITA_TOKEN")
if not token:
print("エラー: QIITA_TOKEN が環境変数に設定されていません。", file=sys.stderr)
return None
url = "https://qiita.com/api/v2/items"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
payload = {
"title": title,
"body": body,
"tags": tags,
"private": private
}
try:
response = requests.post(url, headers=headers, json=payload, timeout=10)
# レート制限情報の取得
rate_remaining = response.headers.get("Rate-Remaining")
rate_limit = response.headers.get("Rate-Limit")
print(f"[API Rate Limit Status] 残り: {rate_remaining} / 上限: {rate_limit}")
# ステータスコードに応じた処理
if response.status_code == 201:
print("成功: 記事が正常に投稿されました。")
return response.json()
elif response.status_code == 400:
print("エラー (400 Bad Request): リクエストパラメータが無効です。必須項目や型を確認してください。")
print(f"詳細: {response.text}")
elif response.status_code == 401:
print("エラー (401 Unauthorized): 認証に失敗しました。アクセストークンを確認してください。")
elif response.status_code == 403:
print("エラー (403 Forbidden): 権限がありません。トークンに 'write_qiita' スコープがあるか確認してください。")
elif response.status_code == 429:
reset_time = response.headers.get("Rate-Reset")
print(f"エラー (429 Too Many Requests): APIのレート制限に達しました。リセット時刻(UNIX): {reset_time}")
else:
print(f"エラー (HTTP {response.status_code}): {response.text}")
except requests.exceptions.Timeout:
print("エラー: リクエストがタイムアウトしました。", file=sys.stderr)
except requests.exceptions.RequestException as e:
print(f"通信エラーが発生しました: {e}", file=sys.stderr)
return None
if __name__ == "__main__":
sample_tags = [{"name": "Python"}, {"name": "Qiita"}]
result = post_qiita_item_safe(
title="安全な投稿処理のテスト",
body="## 本文\nエラーハンドリングの動作テストです。",
tags=sample_tags,
private=True
)
if result:
print(f"投稿完了 URL: {result.get('url')}")
Qiita Team専用機能と個人版Qiitaのパラメータ・API差分
Qiita API v2は、個人向けの「Qiita(qiita.com)」と、組織向けの「Qiita Team(*.qiita.com)」の両方の操作に対応しています。しかし、両者では利用可能なパラメータやエンドポイントの機能に一部違いがあります。
特に記事投稿・グループ管理に関わるパラメータには、Qiita Team専用のものが多く存在するため、個人版Qiitaを利用する際は混同しないよう注意が必要です。
個人版QiitaとQiita Teamの機能・パラメータ比較
| 機能・パラメータ | 個人版 Qiita | Qiita Team | 補足・仕様解説 |
|---|---|---|---|
private パラメータ |
○ | △(挙動が異なる) | 個人版Qiitaでは true で「限定共有」となります。Qiita Teamでは記事の公開範囲はグループやチーム設定に依存します。 |
coediting (共同推敲) |
× | ○ (Team専用) | 複数人で同一記事を編集できるモードです。個人版Qiitaで指定しても無視されるか利用できません。 |
group_url_name / group
|
× | ○ (Team専用) | 記事をTeam内の特定のグループ(例: dev)に紐付ける設定です。 |
ドメイン (Host) |
qiita.com |
subdomain.qiita.com |
APIリクエスト時のエンドポイントホスト名が異なります。 |
| チームアクセストークン | × | ○ (Team専用) | Team用の認証エンドポイント(/api/v2/team_access_tokens)およびSSO認証対応トークン。 |
| 絵文字リアクション | × | ○ (Team専用) | 記事やコメントへの絵文字追加(/api/v2/items/:item_id/reactions)はTeam専用機能です。 |
| テンプレート展開 | × | ○ (Team専用) | テンプレート変数展開(/api/v2/expanded_templates)はTeam専用機能です。 |
Qiita API利用時の注意点と非推奨・廃止APIの整理
Qiita API v2は長年運用されており、過去の仕様更新に伴って廃止されたエンドポイントや非推奨となった機能が存在します。古い技術記事や仕様書を参考に実装する場合、すでに存在しないAPIを呼び出してエラーとなるケースがあるため注意が必要です。
主な廃止済み機能・エンドポイント一覧
-
Project(プロジェクト)機能関連エンドポイントの廃止
- 2022年10月1日をもって、プロジェクト機能および関連API(
/api/v2/projects以下のコメント投稿・リアクション等)は完全に廃止されました。現在は使用できません。
- 2022年10月1日をもって、プロジェクト機能および関連API(
-
Thank(感謝)/ Team Like機能の廃止
- 2020年11月4日をもって、コメントに対するThank API(
/api/v2/comments/:comment_id/thank)は廃止されました。
- 2020年11月4日をもって、コメントに対するThank API(
-
OAuth認証での Team スコープ指定ページの変更
- 2020年6月5日以降、
read_qiita_teamやwrite_qiita_teamスコープを要求するOAuth認可ページとして/api/v2/oauth/authorizeを使用することは非推奨(非対応)となり、/api/v2/oauth/team_authorizeへ移行されました。
- 2020年6月5日以降、
ページネーション指定の標準仕様
一覧取得系API(例: GET /api/v2/authenticated_user/items)を利用する場合のページネーションパラメータ仕様は以下の通り決められています。
-
page: ページ番号。指定可能範囲は 1 ? 100(デフォルト: 1) -
per_page: 1ページあたりの取得件数。指定可能範囲は 1 ? 100(デフォルト: 20)
総件数はレスポンスヘッダーの Total-Count、次ページへのリンクは Link ヘッダーから取得するのが公式の標準的な実装方法です。
まとめ
Qiita API v2を利用して記事投稿自動化スクリプトを作成する際は、公開範囲の制御とパラメータ仕様の理解が最重要です。
-
即時公開を防ぐ:
POST /api/v2/itemsではデフォルトで全体公開(private: false)となるため、下書き・検証用途では必ず"private": trueを指定する -
必須パラメータの確認: 投稿時には
title,body,tagsの3項目が必須。tagsはオブジェクトの配列形式で渡す -
適切なスコープ管理: 個人版Qiitaで記事を書き込む場合、アクセストークンには
write_qiitaスコープが付与されている必要がある -
エラーハンドリングとレート制限: 1時間あたり1,000回のリクエスト制限を意識し、ステータスコード
400,401,403,429に対する例外処理を実装する
以上の仕様を正しく把握し、安全なAPI連携プログラムを構築しましょう。