FastAPIは FastAPI() のインスタンスを作るだけで、/docs にSwagger UI、/redoc にReDoc、/openapi.json にOpenAPIスキーマを自動で公開します。設定ファイルもデコレーターの追加も要りません。
この記事では、FastAPI 0.143.0(2026年10月時点の最新版)で実際に動かした結果をもとに、Swagger UIの表示手順と画面の使い方、説明文やタグの整え方、swagger_ui_parameters の設定、本番環境で隠す・守る方法、表示されないときの原因までを順に説明します。
まとめ:FastAPIのSwagger UI(/docs)の要点
- 表示:
fastapi dev main.pyで起動すると、ターミナルに「Documentation at http://127.0.0.1:8000/docs」と出る。そのURLを開けばSwagger UIが表示される。 - 画面:各エンドポイントの「Try it out」→「Execute」で実リクエストを送れる。認証付きAPIは右上の「Authorize」でトークンを入れて試す。
- 説明の整え方:画面に出る文言はコード側で決まる。
tags・summary・docstring・examplesを書き、表示順はopenapi_tagsで指定する。 - 画面の挙動:
FastAPI(swagger_ui_parameters={...})にSwagger UIの設定キーをそのまま渡す。認証の保持はpersistAuthorization、一覧を畳むならdocExpansion。 - 本番:
docs_url=None, redoc_url=Noneだけでは/openapi.jsonが200で残る。全部止めるならopenapi_url=None。社内にだけ見せるならBasic認証で守る。
FastAPIでSwagger UIを表示する手順
FastAPIの最新版は0.143.0で、Python 3.10以上が必要です(PyPIのfastapi)。公式チュートリアルは pip install "fastapi[standard]" で入れ、fastapi dev コマンドで起動する手順に移っています。素の pip install fastapi でも fastapi コマンドの入口はできますが、実体の fastapi-cli とUvicornが入らないため、実行すると To use the fastapi command, please install "fastapi[standard]" と出て止まります。
pip install "fastapi[standard]"
# main.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/{item_id}")
def read_item(item_id: int, q: str | None = None):
return {"item_id": item_id, "q": q}
$ fastapi dev main.py
Server started at http://127.0.0.1:8000
Documentation at http://127.0.0.1:8000/docs
起動ログの「Documentation at」の行がSwagger UIのURLです。従来の uvicorn main:app --reload でも同じ画面が出ます。FastAPIはアプリの初期化時に、既定で次の4つのURLを登録します。
| URL | 中身 | 変更・無効化する引数 |
|---|---|---|
| /docs | Swagger UI | docs_url |
| /redoc | ReDoc(閲覧専用の文書) | redoc_url |
| /openapi.json | OpenAPI 3.1.0 のスキーマ | openapi_url |
| /docs/oauth2-redirect | OAuth2認可後の戻り先 | swagger_ui_oauth2_redirect_url |
Swagger UIは /openapi.json をブラウザから読み込んで画面を組み立てています。URLを変えたいときは FastAPI(docs_url="/documentation") のように渡します。Swagger UIそのものの成り立ちやSwagger Editorとの違いはSwaggerとは:Editor・UIをDockerで動かす手順とOpenAPI 3.2対応の現況、FastAPI自体の始め方はFastAPIとは|最短で動かす手順とasync/defの使い分け【2026年版】で扱っています。
Swagger UIの画面でのAPI実行確認
Try it outとExecuteによるリクエスト送信
エンドポイントの行を開き、「Try it out」を押すとパラメータとリクエストボディが編集できるようになります。「Execute」で実際にサーバーへ送信され、下に次の3つが出ます。
- Curl:同じリクエストをターミナルで再現するコマンド。不具合の報告にそのまま貼れる。
- Request URL:クエリ文字列まで含めた送信先。
- Server response:ステータスコード・レスポンス本文・ヘッダー。
パスパラメータやリクエストボディなど入力を受け取るエンドポイントに422が載るのは、FastAPIが型ヒントとPydanticモデルで入力を検証し、違反を422 Unprocessable Entityで返すためです。item_id に文字列を入れて実行すると、どの項目がなぜ弾かれたかが detail に入って返ります。
Authorizeボタンでの認証付きAPIの実行
依存関係に OAuth2PasswordBearer を使うと、OpenAPIスキーマに securitySchemes が追加され、Swagger UIの右上に「Authorize」ボタンが出ます。ユーザー名とパスワードを入れると tokenUrl にフォーム送信してトークンを受け取り、以後のリクエストに Authorization: Bearer ヘッダーを付けて送ります。
from typing import Annotated
from fastapi import Depends, FastAPI
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
app = FastAPI(swagger_ui_parameters={"persistAuthorization": True})
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@app.post("/token")
def login(form: Annotated[OAuth2PasswordRequestForm, Depends()]):
# 動作確認用:OAuth2PasswordBearerはトークンを取り出すだけで検証しない。
# 本番では発行時にパスワードを検証して署名付きトークンを返し、受信側でも署名と期限を検証する
return {"access_token": form.username, "token_type": "bearer"}
@app.get("/me")
def read_me(token: Annotated[str, Depends(oauth2_scheme)]):
return {"token": token}
トークン無しで /me を叩くと401と {"detail": "Not authenticated"} が返ります。persistAuthorization を True にすると、ブラウザを再読み込みしても入力したトークンが消えません(Swagger UIの既定は false)。
Swagger UIの説明・タグ・入力例の設定
Swagger UIに表示される文言は、すべてコード側の書き方で決まります。画面を直接編集する手段は無いので、ドキュメントの質はパス操作の引数とPydanticモデルの書き方で上げます。
from typing import Annotated
from fastapi import Body, FastAPI
from pydantic import BaseModel, Field
tags_metadata = [
{"name": "users", "description": "ユーザーの登録と参照"},
{"name": "items", "description": "商品の操作"},
]
app = FastAPI(
title="在庫API",
version="1.2.0",
summary="社内向け在庫管理API",
openapi_tags=tags_metadata,
)
class Item(BaseModel):
name: str = Field(examples=["ボールペン"])
price: int = Field(gt=0, description="税込価格(円)", examples=[120])
@app.post("/items", tags=["items"], summary="商品を登録する", status_code=201)
def create_item(
item: Annotated[Item, Body(openapi_examples={
"normal": {"summary": "通常", "value": {"name": "ノート", "price": 200}},
"invalid": {"summary": "価格0は422", "value": {"name": "ノート", "price": 0}},
})],
) -> Item:
"""商品を1件登録します。
- **price** は1以上
"""
return item
@app.get("/users/{user_id}", tags=["users"], deprecated=True)
def read_user(user_id: int):
return {"user_id": user_id}
@app.get("/healthz", include_in_schema=False)
def healthz():
return {"ok": True}
| 書き方 | Swagger UIでの出方 |
|---|---|
| FastAPI(title, version, summary) | 画面最上部の見出し |
tags=["items"] |
タグごとの折りたたみ単位 |
| openapi_tags | タグの説明文と表示順 |
| summary | エンドポイント行の短い説明 |
| docstring | 展開後の説明(Markdown可) |
| Body(openapi_examples=…) | 例の切り替えドロップダウン |
| deprecated=True | 取り消し線で非推奨表示 |
| include_in_schema=False | 画面にもスキーマにも出ない |
上のコードでは items のルートを先に定義していますが、画面では openapi_tags の並びどおり users が先に出ます。タグの並びを業務の流れに合わせたいときはここで決めます。openapi_examples は複数の例を「正常系」「422になる例」のように並べられるので、受け手が境界値をその場で試せます。ヘルスチェックのように利用者に見せる必要がないものは include_in_schema=False で外します。外部に渡すAPI仕様書としてどこまで書き込むかはAPI設計書の書き方|記載項目チェックリストとOpenAPIサンプル・レビュー観点の観点が使えます。
swagger_ui_parametersの既定値と画面設定
swagger_ui_parameters に渡した辞書は、Swagger UIの設定としてそのままHTMLに書き出されます。FastAPIは何も渡さなくても次の5つを既定で入れています(fastapi/openapi/docs.py の swagger_ui_default_parameters)。
{
"dom_id": "#swagger-ui",
"layout": "BaseLayout",
"deepLinking": True,
"showExtensions": True,
"showCommonExtensions": True,
}
渡した辞書はこの既定に上書きでマージされます。よく使うキーは次のとおりです。値の意味と既定値はSwagger UIの公式設定一覧に従います。
| キー | Swagger UI既定 | 変えると |
|---|---|---|
| docExpansion | "list" |
"none"で全部畳む |
| defaultModelsExpandDepth | 1 | -1で下部のSchemas欄を隠す |
| persistAuthorization | false | 再読み込みでもトークン保持 |
| filter | false | タグの絞り込み欄を出す |
| tryItOutEnabled | false | 最初から入力欄を開く |
| displayRequestDuration | false | 応答時間(ms)を表示 |
| syntaxHighlight.theme | "agate" |
"obsidian"等の7種から選ぶ |
app = FastAPI(
swagger_ui_parameters={
"docExpansion": "none",
"defaultModelsExpandDepth": -1,
"filter": True,
"syntaxHighlight.theme": "obsidian",
}
)
syntaxHighlight.theme はキー名にドットを含む文字列のまま渡します。"syntaxHighlight": False にすると色付けそのものを切れるので、大きなレスポンスで画面が重いときに効きます。関数を渡す設定(リクエストを書き換える requestInterceptor など)はJSONにできないため、この引数では指定できません。get_swagger_ui_html も設定をJSONに変換して書き出すため、関数を使いたい場合は docs_url=None にして、JavaScriptの関数を直接書いたHTMLを自前のルートから返します。
本番環境でのSwagger UIの非公開化と閲覧制限
docs_url・redoc_url・openapi_urlの無効化範囲
「本番では docs_url=None, redoc_url=None にする」という手順が広く紹介されていますが、これで消えるのは画面だけです。FastAPI 0.143.0で各設定の応答を確かめた結果が次の表です。
| 設定 | /docs | /redoc | /openapi.json |
|---|---|---|---|
| 既定 | 200 | 200 | 200 |
| docs_url=None, redoc_url=None | 404 | 404 | 200 |
| openapi_url=None | 404 | 404 | 404 |
画面を消しても、スキーマのJSONには公開対象のエンドポイント・パラメータ・モデルが残り、ネットワークや認証で制限していなければ誰でも取得できます。仕様を外に出したくないなら openapi_url=None を使います。FastAPIのソースでも、openapi_url が無いときは /docs と /redoc を登録しない作りになっています。
環境変数によるOpenAPI公開設定の切り替え
公式のConditional OpenAPIは、設定クラスに openapi_url を持たせ、環境変数で空にする方法を示しています。pydantic-settings は fastapi[standard] に含まれます。
from fastapi import FastAPI
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
openapi_url: str = "/openapi.json"
settings = Settings()
app = FastAPI(openapi_url=settings.openapi_url)
OPENAPI_URL= fastapi run main.py
空文字を渡すと /openapi.json・/docs・/redoc の3つとも404になります。同じ公式ページは、ドキュメントを隠してもAPIの安全性は上がらない(エンドポイントは残る)と明記しています。隠すのは仕様を外に出さないためであって、認可の代わりにはなりません。
Basic認証によるドキュメントの閲覧制限
社内の開発者や連携先など、資格情報を渡した相手にだけ仕様を見せたい場合は、自動生成を止め、認証付きのルートで同じ画面とスキーマを返します。
import secrets
from typing import Annotated
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.openapi.docs import get_swagger_ui_html
from fastapi.openapi.utils import get_openapi
from fastapi.security import HTTPBasic, HTTPBasicCredentials
app = FastAPI(docs_url=None, redoc_url=None, openapi_url=None)
security = HTTPBasic()
def verify(cred: Annotated[HTTPBasicCredentials, Depends(security)]):
ok_user = secrets.compare_digest(cred.username.encode(), b"admin")
ok_pass = secrets.compare_digest(cred.password.encode(), b"change-me")
if not (ok_user and ok_pass):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
headers={"WWW-Authenticate": "Basic"},
)
@app.get("/docs", include_in_schema=False)
def docs(_: Annotated[None, Depends(verify)]):
return get_swagger_ui_html(openapi_url="/openapi.json", title="API docs")
@app.get("/openapi.json", include_in_schema=False)
def openapi_schema(_: Annotated[None, Depends(verify)]):
return get_openapi(title="API", version="1.0.0", routes=app.routes)
資格情報なしでは /docs も /openapi.json も401(WWW-Authenticate: Basic 付き)になり、ブラウザが入力ダイアログを出します。認証が通ると画面もスキーマも返り、スキーマには業務のエンドポイントだけが載ります。secrets.compare_digest を使うのは、文字列比較の処理時間から正解を推測される攻撃を避けるためです。実運用ではユーザー名とパスワードを環境変数から読み、TLSの内側で使ってください。
判断の目安は次のとおりです。外部の開発者に使ってもらう公開APIなら、利用者が仕様を参照できるように隠さず出します。社内・取引先限定のAPIはBasic認証かVPN内に限定します。スキーマの存在自体を知られたくない管理用APIは openapi_url=None にし、仕様書は別途管理します。
Swagger UIが表示されない・読み込めないときの原因
プロキシ配下で「Failed to load API definition」
Nginxなどで /api 配下にFastAPIを置くと、画面は出るのにスキーマが読めないことがあります。Swagger UIはブラウザから /openapi.json を取りに行くので、プレフィックスの付かないドメイン直下を叩いて404になるためです(公式のBehind a Proxy)。プロキシがプレフィックスを取り除いてFastAPIへ転送する構成では、root_path を指定すると解消します。
fastapi run main.py --root-path /api
FastAPI(root_path="/api") で確かめると、Swagger UIのHTMLが読み込むURLは /api/openapi.json に変わり、スキーマにも servers: [{"url": "/api"}] が入るので、Try it outの送信先も揃います。
Dockerコンテナ内の待ち受けアドレス
fastapi dev の待ち受けは既定で 127.0.0.1、fastapi run は 0.0.0.0 です(--help の表示)。コンテナ内で fastapi dev を動かすとホスト側のブラウザから届かないので、--host 0.0.0.0 を付けるか fastapi run を使います。Dockerfileやcomposeでの本番構成はFastAPI×ReactのDocker本番デプロイ|uv対応Dockerfileとcompose構成、Uvicornの起動オプションはUvicornとは?FastAPIを動かすPython製ASGIサーバーの基礎から本番デプロイまでにまとめています。
CDN遮断時の描画不良と静的ファイルの自前配信
FastAPIが返すSwagger UIのHTMLは、本体のJavaScriptとCSSを https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/ から読み込みます。閉域網やCDNを遮断したネットワークでは、HTMLは200で返るのに画面が描画されません。
このURLはメジャー番号の @5 だけを指定しているため、実際に読まれるのはその時点の5系の最新版(2026年10月12日時点で5.33.1)です。FastAPIを更新していなくても、Swagger UIの版はある日変わります。画面の見え方を固定したい場合や、Content-Security-Policyで読み込み元を絞る場合は、版を固定したURLか自前の静的ファイルを指定します。
from fastapi import FastAPI
from fastapi.openapi.docs import (
get_swagger_ui_html,
get_swagger_ui_oauth2_redirect_html,
)
from fastapi.staticfiles import StaticFiles
app = FastAPI(docs_url=None)
# static/ に swagger-ui-bundle.js と swagger-ui.css を置く
app.mount("/static", StaticFiles(directory="static"), name="static")
@app.get("/docs", include_in_schema=False)
def custom_docs():
return get_swagger_ui_html(
openapi_url=app.openapi_url,
title=app.title + " - Swagger UI",
oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url,
swagger_js_url="/static/swagger-ui-bundle.js",
swagger_css_url="/static/swagger-ui.css",
)
@app.get(app.swagger_ui_oauth2_redirect_url, include_in_schema=False)
def swagger_ui_redirect():
return get_swagger_ui_oauth2_redirect_html()
静的ファイルは https://cdn.jsdelivr.net/npm/[email protected]/swagger-ui-bundle.js のように版を固定したURLから取得しておきます。外部へ出られる環境で版だけ固定したいなら、swagger_js_url と swagger_css_url にこのCDNのURLを直接渡せば足ります。手順全体は公式のCustom Docs UI Static Assetsにあります。
サブアプリによるSwagger UIとスキーマの分割
エンドポイントが数百本になると、1枚のSwagger UIでは目的のAPIを探しにくくなります。FastAPIはアプリごとにSwagger UIを持つので、機能単位のサブアプリを mount すると画面が分かれます。
from fastapi import FastAPI
app = FastAPI(title="Base")
admin = FastAPI(title="Admin")
@admin.get("/users")
def list_users():
return []
app.mount("/admin", admin)
この構成では /admin/docs に管理用のSwagger UIが独立して出て、読み込むスキーマは /admin/openapi.json になります。一方、親アプリの /openapi.json には /admin/users が含まれません。スキーマから型定義やクライアントを生成している場合、mountで分けると生成対象も分かれる点に注意してください。画面を探しやすくしたいだけなら、まずはタグと filter で足ります。mountは認証方式や公開範囲が機能ごとに違うときに使います。
PythonでSwagger UIを使う方法:フレームワーク別の違い
PythonでSwagger UIを出す方法はフレームワークで異なります。FastAPIは本体に組み込まれていますが、DjangoとFlaskは追加パッケージが必要です。
| フレームワーク | Swagger UIの出し方 | 最新版(PyPI) |
|---|---|---|
| FastAPI | 標準で /docs | 0.143.0 |
| Django REST framework | drf-spectacular の SpectacularSwaggerView | 0.30.0 |
| Flask | flask-smorest の OPENAPI_SWAGGER_UI_PATH | 0.47.0 |
| Flask系 | APIFlask は標準で /docs | 3.1.2 |
| Flask | flasgger | 0.9.7.1(2023年5月) |
flasggerは安定版の最終リリースが2023年5月(開発版も2023年6月)で止まっているので、新規に採用するならflask-smorestかAPIFlaskを選びます。違いはモデルとエンドポイントの結び付け方にあります。FastAPIは関数の引数の型ヒントとPydanticモデルからそのままスキーマを作ります。APIFlaskもPydanticに対応していますが、入出力のスキーマはデコレーターで指定します。drf-spectacularはシリアライザから、flask-smorestはmarshmallowのスキーマから仕様を組み立てます。OpenAPIの仕様そのものとSwaggerとの名前の関係はOpenAPIとは?Swaggerとの違いと仕様書(YAML/JSON)の書き方|3.0・3.1・3.2の差分で整理しています。
既存システムとの連携を前提にAPIの設計からまとめて任せたい場合は、一想のAPI開発・システム連携でも対応しています。
よくある質問
FastAPIのSwagger UIのURLはどこですか?
既定は http://127.0.0.1:8000/docs です。fastapi dev の起動ログに「Documentation at」として表示されます。docs_url を変えていればそのパス、root_path を設定したプロキシ配下ではプレフィックス付きのパスになります。
/docsのURLを変更するにはどうしますか?
FastAPI(docs_url="/documentation") のように渡します。ReDocは redoc_url、スキーマは openapi_url で同じように変えられます。openapi_url を変えた場合も、Swagger UIは新しいURLを自動で読みに行きます。
Swagger UIとReDocはどう違いますか?
どちらも同じ /openapi.json から作られます。Swagger UIは「Try it out」でその場でリクエストを送れる操作向けの画面で、ReDocは3ペインの読みやすさを優先した閲覧専用の文書です。その場で試す開発中の確認にはSwagger UI、読み物として渡す外部向けの仕様共有にはReDocが向いています。
FastAPIが出力するOpenAPIのバージョンはいくつですか?
FastAPI 0.143.0の既定はOpenAPI 3.1.0です。3.1に対応していない古いツールに渡す必要があるときは app.openapi_version = "3.0.2" のように上書きできます。ただし変わるのはバージョン表記だけで、スキーマの中身は3.0形式へ変換されません。3.1で追加された書き方が混じっていれば相手側で検証エラーになるため、変更後にそのツールでスキーマを検証してください。
swagger_ui_parametersで設定できないものはありますか?
JSONにできない値、つまりJavaScriptの関数を渡す設定は指定できません。requestInterceptor などを使いたい場合は、docs_url=None にして、関数を直接書いたHTMLを自前のルートから返します。