このブログは何か月も同じことを繰り返してきた。パスワードはブラウザから出すべきでは
ない。生成ツールは
crypto.getRandomValues() から乱数を取り、それはあなたの端末の中で起きる。
チェッカーも、入力された文字をどこにも送らずに解析する。
そして今日、サーバー側でパスワードを生成する API を公開する。
矛盾は明らかで、ごまかすつもりはない。API のすべての応答が、まさにそのことを
告げる注意書きを載せている。しかしブラウザが存在しない場所がある——100 個の
アカウントを作るスクリプト、一時的な認証情報を発行するサーバー、パスワードを
求められて創作してしまうアシスタント——そこでの選択肢は「ブラウザ」ではない。
選び方を誤った random() か、手で打った文字列だ。この API はそのためにある。
呼び出しの全部
キーなし、登録なし、ヘッダーなし。これで全部だ。
curl -X POST https://api.password.es/v1/generate
16 文字のパスワードとその解析が返る。この段落を書きながら実行した結果は
x*7$9,BUy9PPvOI?、89 文字のアルファベットから引かれ、エントロピー
103.6 ビット、解読時間は 2.5 × 10¹¹ 年。レベルは 4 段階中の 4。
これらの数値はどれも新しくない。トップページのメーターを描くのと同じエンジンから 出ている。ビットの式も、攻撃モデル——毎秒 10¹² 回の試行、オフライン、高速ハッシュ——も、レベルの尺度もチェッカーと同じだ。 同じパスワードでサイトと API が違う数値を出すなら、どちらかが嘘をついていること になる。
返ってくる中身、フィールドごとに
応答には passwords——1 個だけ頼んでも必ず配列——と、length、pool、bits、
log10_guesses、crack_time_log10_seconds、crack_time、level、
level_scale、ceiling を含む analysis ブロックが入る。
そのうち二つは説明に値する。level_scale はレベルの出どころを示す(今日は
常に "time")。将来サイトと API が食い違ったとき、数値を突き合わせて推測する
のではなく、フィールドを見れば分かるようにするためだ。そして ceiling は
その数値が厳密なのか上限なのかを示す。generate では常に false になる。
サーバーがパスワードを作り、どのアルファベットを使ったか知っているからだ。
サイトのチェッカーではそうとは限らない。あちらではパスワードを持ち込むのは
あなただからだ。
続いて注意書きの notice、そしてプラン、適用された言語、制限、ドキュメントへの
リンクを含む _meta ブロックが入る。
オプション
すべて任意で、名前は英語のまま。API を組み込む人はフィールド名をそのまま打つ
からだ。length(4–64、既定 16)、count(1–20)、lower、upper、digits、
symbols、exclude_ambiguous、no_repeats、lang。
curl -X POST https://api.password.es/v1/generate -H 'content-type: application/json' -d '{"length":20,"exclude_ambiguous":true}'
ドキュメントに書かれていて、組み込む前に知っておくとよい細部が二つ。
exclude_ambiguous が外すのは 7 文字ではなく 6 文字だ。0 O 1 I l | o を
落とすが、縦棒はそもそも記号セットに入っていない。だからアルファベットは 89 から
83 に下がり、82 にはならない。応答の pool が 83 なのはそのためだ。そして
no_repeats は絶対の保証ではない。サイトの生成ツールとまったく同じく 10 回
やり直すだけで、64 文字では隣り合う繰り返しが約 0.1 % の確率ですり抜ける。
逆を約束するより、こう言うほうが役に立つ。
応答の言語
既定では英語で返る。何も指定せずに組み込む人が期待するのがそれだからだ。変更は
URL の ?lang=、本文の "lang"、ヘッダーの Accept-Language の三通りで、
食い違ったときは先のものが勝つ。存在しない言語はエラーではなく、英語に落ちる。
ここには推測できない非対称がある。だから API 自身が申告する。メッセージは
英語とスペイン語にしか存在せず、リンクはサイトの 18 言語すべてにある。ドイツ語を
指定すればリンクはドイツ語、メッセージは英語のままだ。推測は要らない。
_meta.lang が { "messages", "links" } として、どちらに何が適用されたかを
返してくる。
MCP サーバー
MCP は、アシスタント——Claude や ChatGPT——が外部ツールを使うためのプロトコル
だ。https://api.password.es/mcp は登録もキーも不要な MCP サーバーで、公開して
いるツールは一つだけ。上と同じパラメータを取る generate_password である。
ここが我々にとって一番重要な部分で、技術的な理由からではない。パスワードを
取ってくる先がないアシスタントは、それを創作する。そこから出てくるものは
ランダムではなく、モデルが「パスワードらしい」と考えた何かだ。このサーバーを
つないでおけば、アシスタントは即興をやめ、crypto.getRandomValues() で作られた
ものを、解析と、ツールの説明および各結果に含まれる注意書きとともに返す。その
注意書きこそ、アシスタントがパスワードを求めた人に読み上げることになる文言だ。
だからそこに置いてある。
他の MCP サーバーから来たなら、一つだけ。これはステートレスだ。 POST には
JSON で応答し、ストリームは開かない。Mcp-Session-Id は発行も期待もしない。
通知には本文なしの 202 を返し、GET は 405 を返す。2025-06-18 の仕様はこれを
明示的に許しているが、セッションと SSE を前提にしている人は、誰も教えてくれ
なければ手探りでデバッグすることになる。
できないこと
/v1/check は 501 を返す。 手抜きでも作りかけでもなく、意図的にそうして
あり、応答自身が理由を説明する。チェッカーと同じ数値を返すには、サイトで動いて
いるのと同じパターン解析エンジンが要る。そしてそれは、送られてくる内容次第で
1 リクエストあたり 11 ms から 3.6 秒の CPU を食う。登録もキーもないサービスで、
この幅は製品上の判断——長さの上限をどこに置くか——であり、まだ決まっていない。
それまでの間、501 は要求された言語でサイトのチェッカーを指す checker_url を
返す。あちらは同じことを、何も送らずにやってくれる。
アカウントもキーもプランも存在しない。存在しないので、API のどこにも登録への リンクは一つもない。誰もが必ず置く「制限超過エラー」の中にすらない。
制限
一つだけ、そして実際に適用されているのはそれだ。IP ごとに毎分 60 リクエスト。
このページを信じる必要はない。数値はすべての応答の _meta.limits.burst に入って
運ばれてくる。超えると Retry-After と RateLimit-* ヘッダー付きの 429 が返る。
_meta にはもう一つ、値が null の quota ブロックが見える。アカウントが
できたときのために予約された枠で、意図的に空にしてある。宣言されていながら
強制されていない制限は、何も宣言しないより悪い。組み込む側はそれを守り、誰も
数えていない数値に向けてコードを書くことになるからだ。
それでも、二度考えてほしい
パスワードを生成する API は、根本的にはアンチパターンだ。パスワードがネットワークを 通り、あなたのものではない機械を通過する。我々が何も保存しないことは、問題の形を 変えず、我々の担当分を変えるだけだ。そして 検証できない約束にどれだけの価値があるかは もう知っているはずだ。
だから注意書きはこのページだけに置いていない。要求された言語で、すべての応答に 乗って運ばれる。そしてだからこの段落は、ドキュメントの末尾に隠さずここにある。 自分で使うパスワードなら、このサイトの生成ツールはすべてブラウザの中で 動き、何も送らない。 API はもう一方のためのものだ。画面の前に誰もいないところで 起きることのために。
詳細は 2 ページに分かれている。API とは何かは、誰のためのもので、
今日何が応答し何がしないかを説明する。リファレンスには 9 つの
パラメータ、すべてのフィールドの逐一の説明、エラーコード、制限がある。
エディタの横に開いておくのはこちらだ。機械に読ませたいなら openapi.json が
ある。
出典:API 本体、2026 年 8 月 31 日に本番環境に対して検証——POST /v1/generate、
POST /v1/check(501)、POST /mcp、GET /openapi.json · 解析の数値は
password.es の生成ツールおよびチェッカーと同じエンジンから出ている · 攻撃モデルは
毎秒 10¹² 回の試行、オフライン、高速ハッシュで、サイトの他の部分と同じ ·
MCP サーバーは 2025-06-18 の仕様を、ステートレスな Streamable HTTP トランスポート
で実装している。
写真: Stanislav Kondratiev · Pexels