Qiita API経由で実行した記事削除処理は即時に完全実行され、下書きへの退避や復元は一切不可能です。
-
削除操作は完全不可逆:
DELETE /api/v2/items/:item_idを実行すると記事本文だけでなく、ストックやいいね、コメントも復元不能になります。 -
適切なスコープの確認が必須:個人Qiitaでは
write_qiita、Qiita Teamではwrite_qiita_teamの権限を持つアクセストークンが必要です。 - 事前バックアップと判定ロジックを実装:記事ID(20桁の16進数)の正確な特定と、削除前のローカル保存・対話的確認の組み込みが事故防止に繋がります。
Qiita APIにおける記事削除(DELETE)の基本仕様と注意点
Qiita API v2を利用して記事を削除する際、最も留意すべき点は「削除操作の不可逆性」です。WebUIからの削除と同様に、APIで削除リクエストが成功した時点で対象のデータはQiitaのデータベースから消去され、元に戻す手段は提供されていません。
記事削除のエンドポイントは以下の通りです。
DELETE /api/v2/items/:item_id
削除リクエストが正常に受け付けられると、APIサーバーは HTTP 204 No Content を返却します。この際、レスポンスボディには何も含まれません。処理が成功したかどうかはHTTPステータスコードによって判定する必要があります。
削除処理で消失するデータ一覧
記事を削除すると、単に本文(Markdown)やレンダリング結果(HTML)が消えるだけではありません。その記事に紐づくすべての関連データが同時に失われます。
- 記事のタイトル、本文(Markdown/HTML)、タグ情報
- 獲得した「いいね(Like)」数および「ストック(Stock)」数
- 記事に対して投稿された全コメントデータ
- 閲覧数(Page Views)などのアナリティクスデータ
削除実行後に同じ内容を再投稿しても、新しい :item_id が割り当てられるため、過去のURLや獲得した「いいね」「ストック」を流用することは不可能です。
HTTPステータスコード早見表
Qiita API v2との通信において返却される主なステータスコードと、削除処理における意味は以下の通りです。
| ステータスコード | 意味 | 削除処理における状態・原因 |
|---|---|---|
| 200 OK | 成功(データあり) | GETやPATCH等で成功した場合の返却値。DELETE成功時は204 |
| 201 Created | 作成完了 | POST等でリソースが新しく作成された場合の返却値 |
| 204 No Content | 処理成功(ボディなし) | DELETE /api/v2/items/:item_id の成功レスポンス |
| 400 Bad Request | リクエスト不正 | リクエスト形式やパラメータの指定に誤りがある状態 |
| 401 Unauthorized | 未認証 | Authorizationヘッダーがない、またはトークンが無効 |
| 403 Forbidden | 権限不足 | トークンに必要なスコープ(write_qiita等)が付与されていない |
| 404 Not Found | リソース未存在 | 指定した :item_id が存在しない、または既に削除済み |
| 429 Too Many Requests | レート制限超過 | 1時間あたりのAPI呼び出し上限(1000回)を超過 |
| 500 Internal Server Error | サーバー内部エラー | Qiita側のシステムエラー。一時的な障害の可能性あり |
実行前に必須!アクセストークンのスコープと権限チェック
Qiita API v2では、アクセストークンに対して操作範囲を限定する「スコープ(Scope)」が設定されます。適切なスコープが付与されていないトークンを使用すると、認証は通っても HTTP 403 Forbidden エラーが返り、処理が拒否されます。
アクセストークンの4つのスコープ
Qiita API v2で提供されているスコープは以下の4種類です。個人向けの機能とQiita Team向けの機能でスコープが分離されている点に注意してください。
| スコープ名 | 許可される操作の範囲 | 対象サービス |
|---|---|---|
| read_qiita | 認証ユーザーに紐づく個人データの読み取り | 個人向けQiita (qiita.com) |
| read_qiita_team | Qiita Team上のデータの読み取り | Qiita Team (*.qiita.com) |
| write_qiita | 個人向けQiita上のデータ書き込み・修正・削除 | 個人向けQiita (qiita.com) |
| write_qiita_team | Qiita Team上のデータ書き込み・修正・削除 | Qiita Team (*.qiita.com) |
個人向けQiitaで記事を削除するには write_qiita スコープが必要です。一方、組織向けの Qiita Team で記事を削除する場合は write_qiita_team スコープが必須となります。
トークンの安全な管理
アクセストークンをスクリプト内に直接ハードコードすることはセキュリティ上の重大なリスクです。誤ってGitHubなどのパブリックリポジトリにコミットした場合、第三者に記事を改ざん・削除される恐れがあります。
必ず環境変数(例:QIITA_TOKEN)経由でトークンを読み込む設計にしてください。
誤削除を防ぐための記事ID特定と事前バックアップ手順
削除リクエストを発行する前に、削除対象となる記事のID(:item_id)を正確に特定することが極めて重要です。Qiitaの記事IDは c686397e4a0f4f11683d のような20桁の16進数文字列(正規表現:/^[0-9a-f]{20}$/)で構成されています。
投稿記事一覧の取得 API
自分が投稿した記事の一覧を確認・取得するには、以下の公式エンドポイントを使用します。
GET /api/v2/authenticated_user/items
このエンドポイントを呼ぶことで、認証ユーザーが投稿した記事のメタデータやIDのリストをJSON形式で受け取ることができます。
ページネーション(Pagination)の仕様
記事数が多い場合はページネーションを利用して全件を取得します。Qiita API v2のページネーション仕様は以下の通りです。
-
page: 取得するページ番号を指定(1?100、デフォルト: 1) -
per_page: 1ページあたりの取得件数を指定(1?100、デフォルト: 20)
レスポンスヘッダーには全件数を示す Total-Count や、前後ページへのURIを示す Link ヘッダーが含まれます。
Total-Count: 42
Link: <https://qiita.com/api/v2/authenticated_user/items?page=1&per_page=20>; rel="first",
<https://qiita.com/api/v2/authenticated_user/items?page=2&per_page=20>; rel="next",
<https://qiita.com/api/v2/authenticated_user/items?page=3&per_page=20>; rel="last"
事前バックアップの推奨
API経由で記事を削除する自動化スクリプトを作成・実行する場合は、万が一の誤操作に備えて、削除前に記事本文(Markdown)およびタイトル、タグなどのメタデータをローカルファイル(JSON形式やMarkdown形式)に退避させる処理を挟むことを強く推奨します。
レート制限(Rate Limit)の仕様とリクエスト制御
Qiita API v2には、サービスの安定稼働を保つためのレート制限(Rate limit)が設けられています。大量の記事を一括削除したり、一覧取得を繰り返したりする場合は制限に達しないよう制御が必要です。
認証・未認証による制限値の違い
-
認証リクエスト(
AuthorizationヘッダーにBearerトークンを含める場合): 1時間あたり 1000 回 - 未認証リクエスト(IPアドレス単位): 1時間あたり 60 回
記事の削除(DELETE)や自分の一覧取得(/api/v2/authenticated_user/items)は認証必須のエンドポイントであるため、上限は1時間あたり1000回となります。
レート制限の確認方法と429エラーへの対応
各APIレスポンスのHTTPヘッダーには、レート制限の残り回数等が含まれています。残り回数が0になった状態でリクエストを送信すると、サーバーは HTTP 429 Too Many Requests を返します。
429エラーが発生した場合は、一定時間(リセット時刻まで)待機した上で処理を再開するようにスクリプト側でハンドリングする必要があります。
Pythonで安全に記事を取得・バックアップ・削除する実践コード
ここからは、Pythonの requests ライブラリを使用して、記事のバックアップ取得および確認プロセス付きの安全な削除を実行するスクリプト例を解説します。
サンプル1:自分の投稿記事一覧を取得してバックアップ保存する
まず、削除前に自身の投稿記事を取得し、JSONファイルとしてローカルにバックアップするスクリプトです。環境変数 QIITA_TOKEN を参照し、エラーハンドリング(401, 403, 429)を実装しています。
import os
import sys
import json
import requests
def backup_qiita_items():
# 環境変数からトークンを取得
token = os.environ.get("QIITA_TOKEN")
if not token:
print("エラー: 環境変数 QIITA_TOKEN が設定されていません。")
sys.exit(1)
url = "https://qiita.com/api/v2/authenticated_user/items"
headers = {
"Authorization": f"Bearer {token}",
"Accept": "application/json"
}
params = {
"page": 1,
"per_page": 100 # 1?100の範囲で指定
}
try:
response = requests.get(url, headers=headers, params=params, timeout=10)
# ステータスコードに応じた処理
if response.status_code == 200:
items = response.json()
total_count = response.headers.get("Total-Count", "不明")
print(f"取得成功: 全 {total_count} 件中、{len(items)} 件を取得しました。")
# ローカルファイルにJSON保存
backup_file = "qiita_items_backup.json"
with open(backup_file, "w", encoding="utf-8") as f:
json.dump(items, f, ensure_ascii=False, indent=2)
print(f"データを '{backup_file}' に保存しました。")
elif response.status_code == 401:
print("エラー 401: 認証に失敗しました。アクセストークンが無効です。")
elif response.status_code == 403:
print("エラー 403: 権限が不足しています。'read_qiita' スコープが必要です。")
elif response.status_code == 429:
print("エラー 429: レート制限(1000回/時)を超過しました。時間をおいて再実行してください。")
else:
print(f"エラー {response.status_code}: {response.text}")
except requests.RequestException as e:
print(f"通信エラーが発生しました: {e}")
if __name__ == "__main__":
backup_qiita_items()
Pythonによる記事削除(DELETE)の実装とエラーハンドリング
次に、実際に記事を削除するスクリプトです。誤動作による一括削除事故を防ぐため、以下の安全装置を組み込んでいます。
- 削除前に対象記事のIDを使って
GET /api/v2/items/:item_idを呼び出し、記事タイトルとURLを表示する。 - ターミナル上でユーザーに対して明示的な最終確認(y/N)を求める。
-
DELETE /api/v2/items/:item_idを実行し、204 No Content が返ることを確認する。
サンプル2:対話的確認付き記事削除スクリプト
import os
import sys
import requests
def delete_qiita_item():
token = os.environ.get("QIITA_TOKEN")
if not token:
print("エラー: 環境変数 QIITA_TOKEN が設定されていません。")
sys.exit(1)
item_id = input("削除したい記事ID(20桁の16進数)を入力してください: ").strip()
if not item_id:
print("記事IDが入力されなかったため、処理を中止します。")
sys.exit(1)
headers = {
"Authorization": f"Bearer {token}",
"Accept": "application/json"
}
# 1. 削除対象の確認(GET)
get_url = f"https://qiita.com/api/v2/items/{item_id}"
try:
get_res = requests.get(get_url, headers=headers, timeout=10)
if get_res.status_code == 404:
print("エラー 404: 指定された記事が見つかりません。IDを確認してください。")
sys.exit(1)
elif get_res.status_code == 401:
print("エラー 401: 認証エラーです。トークンを確認してください。")
sys.exit(1)
elif get_res.status_code == 403:
print("エラー 403: アクセス権限がありません。")
sys.exit(1)
elif get_res.status_code == 429:
print("エラー 429: レート制限を超過しています。")
sys.exit(1)
elif get_res.status_code != 200:
print(f"エラー {get_res.status_code}: 記事情報の取得に失敗しました。")
sys.exit(1)
item_data = get_res.json()
print("\n" + "="*40)
print("【削除対象記事の情報】")
print(f"タイトル : {item_data.get('title')}")
print(f"作成日時 : {item_data.get('created_at')}")
print(f"URL : {item_data.get('url')}")
print("="*40 + "\n")
except requests.RequestException as e:
print(f"通信エラーが発生しました: {e}")
sys.exit(1)
# 2. ユーザーへの最終確認
confirm = input("この記事を本当に削除しますか? 復元はできません。(y/N): ").strip().lower()
if confirm != "y":
print("削除処理をキャンセルしました。")
sys.exit(0)
# 3. 削除リクエストの実行(DELETE)
delete_url = f"https://qiita.com/api/v2/items/{item_id}"
try:
delete_res = requests.delete(delete_url, headers=headers, timeout=10)
if delete_res.status_code == 204:
print("\n成功: 記事が正常に削除されました(204 No Content)。")
elif delete_res.status_code == 401:
print("エラー 401: 認証に失敗しました。")
elif delete_res.status_code == 403:
print("エラー 403: 削除権限がありません。トークンのスコープ(write_qiita)を確認してください。")
elif delete_res.status_code == 404:
print("エラー 404: 対象の記事が存在しないか、既に削除されています。")
elif delete_res.status_code == 429:
print("エラー 429: レート制限を超過しました。")
else:
print(f"エラー {delete_res.status_code}: 削除に失敗しました。")
except requests.RequestException as e:
print(f"通信エラーが発生しました: {e}")
if __name__ == "__main__":
delete_qiita_item()
Qiita Team利用時・廃止機能に関する追加の留意事項
Qiita API v2を利用する際、個人向け機能と「Qiita Team」専用機能の区別、および過去に廃止されたAPIエンドポイントについての正確な知識が必要です。
Qiita Team専用機能とスコープ
以下のリソースおよび操作は Qiita Team専用 機能であり、個人向けQiita (qiita.com) では使用できません。
- グループ機能(
GET /api/v2/groups,POST /api/v2/groupsなど) - テンプレート拡張機能(
POST /api/v2/expanded_templates) - 絵文字リアクション(
POST /api/v2/items/:item_id/reactionsなど) - 記事への反応一覧取得(
GET /api/v2/items/reactions) - チームアクセストークン発行(
POST /api/v2/team_access_tokens) - シングルサインオン(SSO)設定チーム向けアクセス制限
Qiita Team環境で操作を行う場合、リクエスト先ホストが *.qiita.com になるとともに、スコープとして read_qiita_team や write_qiita_team を使用する必要があります。
廃止済み機能(非推奨・利用不可)の整理
過去の仕様変更により、以下の機能やエンドポイントは廃止されています。これらを現行の機能として組み込まないよう注意してください。
-
プロジェクト(Project)機能
-
GET /api/v2/projects関連エンドポイントは、2022年10月1日をもって完全に廃止されました。
-
-
Thank(いいね旧仕様)機能
- コメントに対する Thank 関連エンドポイント(
DELETE /api/v2/comments/:comment_id/thank等)は、2020年11月4日をもって廃止されました。
- コメントに対する Thank 関連エンドポイント(
最新の公式ドキュメントに基づき、存在しないエンドポイントへのリクエストを発行しないよう実装を整備してください。
まとめ
Qiita API v2を使用した記事削除について、重要なポイントを整理します。
-
不可逆な操作:
DELETE /api/v2/items/:item_id実行による削除は即時かつ完全であり、退避領域や復元機能はありません。 -
成功時のレスポンス: 成功時はステータスコード
204 No Contentが返却され、ボディは空となります。 -
スコープの事前確認: 個人Qiitaでは
write_qiita、Qiita Teamではwrite_qiita_teamのスコープが必要です。権限不足時は403 Forbiddenとなります。 -
安全対策の実装: スクリプト作成時は、
GETエンドポイントによる事前確認、ターミナルでの対話型判定、ローカルへのバックアップ保存を組み合わせることで事故を防止できます。
APIによる自動化を行う際は、環境変数でのトークン管理と堅牢なエラーハンドリング(401, 403, 404, 429等)を徹底し、安全に運用しましょう。