マキモノ
SNS運用無料✅ 公式検証済みv1.0.0 / 更新

X(旧Twitter)自動投稿を従量課金時代に立ち上げる手順書

無料枠廃止後のX APIでGitHub Actions定期投稿を構築する。403を生む権限変更とトークン再生成の順番、リンク有無で13倍変わる単価の正しい見積り方、鍵を人に中継させない投入スクリプト、送信前ガードまで。

出品者: nishi@orgiast.jp📖 読込 約4,416トークン (約7円)💰 コスパ 9
トークン節約メーター86%節約
ゼロからAIに作らせた場合4.8万トークン
このMDを読ませた場合6,500トークン

4.2万トークンの節約 (API料金換算で約62円分)。 要件定義・技術調査・試行錯誤ぶんのトークンがまるごと不要になります。※ 出品者申告とレビューに基づく推定値。モデル・タスク内容により変動します。

この巻物について

X(旧Twitter)自動投稿を従量課金時代に立ち上げる手順書」は、SNS運用カテゴリのAI指示書(MDファイル)です。無料枠廃止後のX APIでGitHub Actions定期投稿を構築する。403を生む権限変更とトークン再生成の順番、リンク有無で13倍変わる単価の正しい見積り方、鍵を人に中継させない投入スクリプト、送信前ガードまで。この巻物をAIに読み込ませると、ゼロから設計・調査する場合に比べて 約4.2万トークン(API料金換算で約62円)・86%のトークンを節約できます。

カテゴリ
SNS運用
対応AI
claude-code、cursor、codex-cli
ライセンス
商用利用可 (再販不可)
価格
無料
ゼロから開発時
約4.8万トークン
この巻物使用時
約6,500トークン
節約量
約4.2万トークン (約62円)
更新日
2026-09-16

使い方 (AIに渡す3つの方法)

いちばん簡単なのはワンライナー。Claude Code のターミナルに貼るだけです。

⬇ .md をダウンロード
claude "https://makimono-md.vercel.app/api/v1/files/x-twitter/raw を読み込んで、この指示書どおりに実装して"
claude-codecursorcodex-cliライセンス: 商用利用可 (再販不可)

中身

X(旧Twitter) 自動投稿を従量課金時代に立ち上げる手順書

X API は 2026-02 に無料の書き込み枠が廃止され、新規開発者は PPU(従量課金)のみになった。 この文書は「GitHub Actions の cron から X へ定期投稿する仕組み」を、 課金事故と 403 地獄を踏まずに立ち上げるための手順書。

AI に読ませればそのまま実行できる粒度で書いてある。


0. 先に知っておくべき前提(ここを外すと後で必ず詰む)

単価はリンクの有無で13倍違う

種別単価
通常の投稿$0.015
URL を含む投稿$0.20
読み取り$0.005

投稿本文に URL を入れるか、プロフィール欄のリンクに集約するかで月額が一桁変わる。 着手前に依頼主へ選ばせること。遷移率を取るなら本文URL、コストを取るなら bio 集約。

見積りは「キューの件数」ではなく「実際に発行されるツイート数」で数える

長文は自動でスレッド分割される。1件のキューが2ツイートになれば課金も2回。 しかも分割後の「URLを含むチャンク」だけが $0.20 になる。 必ず本番と同じ分割関数を通してから数えること。件数で見積もると2〜3割ずれる。

# 見積りスクリプトの例(本番の chunk() をそのまま import して数える)
import json, poster
items = json.load(open('queue.json', encoding='utf-8'))
tot = link = plain = 0
for it in items:
    for c in poster.chunk(it['text']):
        tot += 1
        if 'http' in c: link += 1
        else: plain += 1
print('total tweets:', tot)
print('cost to drain queue: $', round(link*0.20 + plain*0.015, 2))

鍵の発行に API は存在しない

Developer Console の画面操作でしか鍵は作れない。CLI も MCP も代行できない。 自動化の設計時に「ここだけは人が1回やる」と割り切って、その1回を最短にする。


1. 最大の罠 — 権限変更とトークン再生成の順番

App permissions を Read and Write に保存した「後」に、Access Token を再生成する。

この順番を逆にすると、発行されたトークンは Write 権限を持たず、投稿が 403 で失敗する。 画面上は全く同じに見えるので気づけない。

正しい順番:

  1. Developer Console → 対象アプリ → 「User authentication settings」→ Set up
  2. App permissions = Read and Write / Type of App = Automated App or Bot / Callback URL と Website URL に自社サイトの URL を入れて 保存
  3. 保存後、「Keys and tokens」に戻り、Access Token の行の表示が Read and Write(日本語UIでは「読み取りと書き込み」)になったことを目視確認
  4. そこで初めて Access Token / Access Token Secret を Regenerate
  5. Consumer Key / Consumer Secret は伏字なので、これも Regenerate して控える

取得する4点:

  • API Key(= Consumer Key)
  • API Key Secret(= Consumer Secret)
  • Access Token
  • Access Token Secret

⚠️ 値を表示するダイアログは閉じると二度と出ない。閉じてしまったら Regenerate をやり直す(何度でも可)。 ⚠️ 「Revoke(取り消す)」と「Regenerate(再生成)」は隣り合っている。Revoke を押すとトークンが消える。


2. 鍵を人の手で中継させない投入ツール

依頼主に鍵をチャットへ貼らせると、会話ログ・スクリーンショット・転送先に残り続ける。 値を画面に出さずに Secrets へ流し込むスクリプトを先に用意して渡すのが正解。

inject-secrets.ps1(Windows / PowerShell):

$repo = "<オーナー>/<リポジトリ>"

# 0) ログイン確認
$who = & gh api user --jq .login 2>$null
if (-not $who) { Write-Host "[NG] gh auth login を実行してください" -ForegroundColor Red; Read-Host; exit 1 }

# 1) admin 権限確認(無ければ届いている招待を自動受諾)
$perm = & gh api "repos/$repo" --jq ".permissions.admin" 2>$null
if ($perm -ne "true") {
  $inv = & gh api user/repository_invitations --jq ".[] | select(.repository.full_name==`"$repo`") | .id" 2>$null
  if ($inv) { & gh api -X PATCH "user/repository_invitations/$inv" | Out-Null
              $perm = & gh api "repos/$repo" --jq ".permissions.admin" 2>$null }
  if ($perm -ne "true") { Write-Host "[NG] $who に admin 権限がありません" -ForegroundColor Red; Read-Host; exit 1 }
}

# 2) 画面非表示で入力
$pairs = [ordered]@{
  "X_API_KEY"       = "API Key (Consumer Key)"
  "X_API_SECRET"    = "API Key Secret"
  "X_ACCESS_TOKEN"  = "Access Token"
  "X_ACCESS_SECRET" = "Access Token Secret"
}
$vals = @{}
foreach ($k in $pairs.Keys) {
  $sec = Read-Host -Prompt "  $($pairs[$k])" -AsSecureString
  $bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($sec)
  $vals[$k] = [Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr).Trim()
  [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr)
}

# 3) 投入して検証
foreach ($k in $pairs.Keys) { & gh secret set $k --repo $repo --body $vals[$k] }
$vals.Clear()
& gh secret list --repo $repo
Read-Host "Enter で終了"

日本語入りの .ps1 は UTF-8 BOM 付きで保存する。 BOM 無しだと Windows PowerShell 5.1 が Shift-JIS と誤読して構文エラーになる。 変数の直後に日本語を続けない$repo に権限がありません$repo に権限がありません 全体を 変数名と解釈して空文字になる)。"$repo" + " に権限がありません" のように分ける。


3. GitHub Actions 側

.github/workflows/post.yml:

name: post-to-x

on:
  schedule:
    - cron: "0 23 * * *"    # 08:00 JST(Actions は UTC)
    - cron: "30 3 * * *"    # 12:30 JST
    - cron: "0 11 * * *"    # 20:00 JST
  workflow_dispatch:

permissions:
  contents: write           # 投稿済みフラグを queue.json に書き戻すため

concurrency:
  group: post-to-x
  cancel-in-progress: false

jobs:
  post:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install -r requirements.txt
      - name: post next queued tweet
        env:
          X_API_KEY: ${{ secrets.X_API_KEY }}
          X_API_SECRET: ${{ secrets.X_API_SECRET }}
          X_ACCESS_TOKEN: ${{ secrets.X_ACCESS_TOKEN }}
          X_ACCESS_SECRET: ${{ secrets.X_ACCESS_SECRET }}
        run: python poster.py
      - name: save posted state
        run: |
          git config user.name "post-bot"
          git config user.email "<ボット用メールアドレス>"
          git add queue.json
          git diff --staged --quiet || git commit -m "chore: mark posted [skip ci]"
          git push

投稿側は OAuth 1.0a を使う(OAuth 2.0 のクライアントID/シークレットでは投稿しない):

client = tweepy.Client(
    consumer_key=os.environ["X_API_KEY"],
    consumer_secret=os.environ["X_API_SECRET"],
    access_token=os.environ["X_ACCESS_TOKEN"],
    access_token_secret=os.environ["X_ACCESS_SECRET"],
)

4. 送信前ガードを必ず入れる(従量課金では必須)

壊れた本文をそのまま「買ってしまう」事故を防ぐ。1投稿 $0.20 の世界では無視できない。

import re
X_MAX_WEIGHTED_LEN = 280
PLACEHOLDER_RE = re.compile(r"(XX+|TODO|TBD|FIXME|<[A-Za-z_ ]{2,}>|<[^>]*>|\{\{)")

def guard_parts(parts):
    """問題の説明リストを返す。空なら投稿してよい。"""
    if not parts:
        return ["本文が空です"]
    problems = []
    for i, part in enumerate(parts, 1):
        label = f"part {i}/{len(parts)}"
        if not part.strip():
            problems.append(f"{label}: 本文が空です"); continue
        if wlen(part) > X_MAX_WEIGHTED_LEN:
            problems.append(f"{label}: 長すぎます")
        if PLACEHOLDER_RE.search(part):
            problems.append(f"{label}: プレースホルダが残っています")
        # 文字化け検出: 日本語0文字なのに Latin-1 補助域が多い=エンコーディング取り違え
        jp = sum(1 for ch in part if 0x3040 <= ord(ch) <= 0x30FF or 0x4E00 <= ord(ch) <= 0x9FFF)
        latin1 = sum(1 for ch in part if 0x0080 <= ord(ch) <= 0x00FF)
        if jp == 0 and latin1 >= 5:
            problems.append(f"{label}: 文字化けの疑い")
    return problems

main() で分割直後・DRY_RUN 判定より前に呼び、問題があれば stderr に出して sys.exit(1)投稿も queue.json の更新もしないこと。


5. クレジットと自動チャージ

  • 開発者登録自体は $0。課金はクレジット購入時のみ
  • 残高切れは 402 Payment Required で静かに止まる。気づくまで数日空く
  • 自動チャージは ON にする(cron が固定回数なら暴走しようがない)。 目安: 月額見積りの1.5〜2ヶ月分をチャージ額、発動しきい値は少額に
  • 購入画面は左メニューから開く。URL を直打ちすると 404 になることがある

6. 立ち上げ後の検証手順

# 手動で1本流す
gh workflow run post.yml --repo <オーナー>/<リポジトリ>

# 結果を見る(成功なら投稿URLがログに出る)
gh run list --repo <オーナー>/<リポジトリ> --limit 1
gh run view <RUN_ID> --repo <オーナー>/<リポジトリ> --log | grep -iE "posted|error|40[0-9]"

エラーの切り分け:

症状原因対処
401 Unauthorizedsecrets が空 or 値の取り違えgh secret list で4件あるか確認 → 再投入
403 Forbidden権限変更前に発行したトークンRead and Write 保存 → その後 Regenerate → 再投入
402 Payment Requiredクレジット残高切れチャージ。自動チャージ ON にする
投稿されるが別アカウントに出る鍵発行時に別アカウントでログインしていた正しいアカウントでログインし直して再発行

ログに X_API_KEY: の右が空で並んでいたら、secrets が渡っていない(登録漏れ or リポジトリ違い)。


7. リポジトリを移設したときの落とし穴

「同じ名前で作り直した」リポジトリは、元のリポジトリと merge-base を持たない別履歴であることが多い。 git diff HEAD <新remote>/main が全ファイル差分になったら、それは移設ではなく作り直し

  • git merge-base HEAD <新remote>/main が空 → 別履歴
  • force push で上書きしない。新しい方を正本と決め、旧版は退避ブランチに残す
git branch legacy-<短縮SHA>                       # 旧版を退避
git remote set-url origin <新リポジトリのURL>
git fetch origin
git checkout -B main origin/main                  # 新しい正本に乗り換え

チェックリスト

  • 本文に URL を入れるか bio 集約かを依頼主に選ばせた(月額が13倍変わる)
  • 本番の分割関数を通してツイート数で見積もった(件数で数えていない)
  • App permissions を Read and Write で保存してから Access Token を Regenerate した
  • トークンの行が Read and Write 表示になっているのを目視確認した
  • 鍵はチャットに貼らせず、画面非表示のスクリプトで Secrets に投入した
  • 送信前ガードを入れた(文字化け・長さ超過・プレースホルダ・空文)
  • 自動チャージを ON にした
  • workflow_dispatch で1本流し、投稿URLがログに出ることを確認した

よくある質問

「X(旧Twitter)自動投稿を従量課金時代に立ち上げる手順書」とは何ですか?

無料枠廃止後のX APIでGitHub Actions定期投稿を構築する。403を生む権限変更とトークン再生成の順番、リンク有無で13倍変わる単価の正しい見積り方、鍵を人に中継させない投入スクリプト、送信前ガードまで。

どれくらいトークン(費用)を節約できますか?

ゼロから開発すると約4.8万トークンかかりますが、この巻物を使えば約6,500トークンで済みます。差し引き約4.2万トークン(API料金換算で約62円)・86%の節約です。

どうやって使いますか?

無料です。MDファイルを Claude Code などのAIに読み込ませるだけ。ワンライナーをターミナルに貼れば実装が始まります。要件定義や技術調査を省いて実装だけにトークンを使えます。

どのAIツールに対応していますか?

claude-code、cursor、codex-cli に対応しています。

商用利用できますか?

ライセンスは「商用利用可 (再販不可)」です。

🤝 自分でAIを動かすのは、まだ不安…という方へ

この巻物の内容を、AIを使うプロに丸ごと任せることもできます。姉妹サービスAI代行堂なら「LINEで頼むだけで、仕事が完成」。

AI代行堂を見る →

関連する巻物

この巻物、誰かのトークンも救えます

𝕏 で節約レシートをシェア