[{"data":1,"prerenderedAt":2560},["ShallowReactive",2],{"page-\u002Fgetting-started-with-python-apis-for-builders\u002Fdocumenting-apis-with-openapi\u002Fredoc-vs-swagger-ui\u002F":3,"faq-schema-\u002Fgetting-started-with-python-apis-for-builders\u002Fdocumenting-apis-with-openapi\u002Fredoc-vs-swagger-ui\u002F":2539},{"id":4,"title":5,"body":6,"description":2529,"extension":2530,"meta":2531,"navigation":480,"path":2535,"seo":2536,"stem":2537,"__hash__":2538},"content\u002Fgetting-started-with-python-apis-for-builders\u002Fdocumenting-apis-with-openapi\u002Fredoc-vs-swagger-ui\u002Findex.md","ReDoc vs Swagger UI",{"type":7,"value":8,"toc":2518},"minimark",[9,13,28,44,49,61,72,75,233,237,240,318,321,324,435,439,447,1156,1170,1173,1683,1698,1702,1705,2152,2168,2172,2175,2178,2294,2298,2306,2401,2409,2425,2429,2438,2442,2448,2454,2464,2470,2476,2480,2514],[10,11,5],"h1",{"id":12},"redoc-vs-swagger-ui",[14,15,16,17,22,23,27],"p",{},"You have a valid OpenAPI document and now you have to decide what a paying customer sees when they open your docs URL. Part of the ",[18,19,21],"a",{"href":20},"\u002Fgetting-started-with-python-apis-for-builders\u002Fdocumenting-apis-with-openapi\u002F","Documenting APIs with OpenAPI"," guide, this page settles the ReDoc versus Swagger UI question with the criteria that actually move revenue: how fast a trial user gets a real ",[24,25,26],"code",{},"200"," back, how readable your reference stays at two hundred operations, what the page costs to serve, and how much of your brand survives the theming layer.",[14,29,30,31,35,36,39,40,43],{},"Both renderers read the same spec, so this is not a correctness question. ",[18,32,34],{"href":33},"\u002Fgetting-started-with-python-apis-for-builders\u002Fsetting-up-fastapi\u002F","FastAPI"," already wires both up for free at ",[24,37,38],{},"\u002Fdocs"," and ",[24,41,42],{},"\u002Fredoc",", which is precisely why most builders never think about it and ship whichever default they saw first. That default costs you either conversions or support tickets, depending on which one you left on. The right answer for a commercial API is usually \"both, on purpose, with different jobs\" — and the rest of this page shows how to make that cheap.",[45,46,48],"h2",{"id":47},"two-renderers-with-two-different-jobs","Two renderers with two different jobs",[14,50,51,52,56,57,60],{},"Swagger UI is an execution surface. It renders each operation as a collapsible card with a ",[53,54,55],"strong",{},"Try it out"," button that fires a real ",[24,58,59],{},"fetch()"," from the reader's browser to your live API. That means a prospect can paste a key, hit send, and see a real response body inside sixty seconds of landing on your site. It also means your docs page generates authenticated, billable, CORS-preflighted traffic against production, and that your customer's key now lives in a browser tab on a page you rendered.",[14,62,63,64,67,68,71],{},"ReDoc is a reading surface. It renders a three-pane reference — navigation on the left, prose and schemas in the middle, request and response samples on the right — and it never calls your API. It gives you generated ",[24,65,66],{},"curl"," and language samples the reader copies into their own terminal, deep-linkable anchors for every operation, and a schema viewer that expands nested ",[24,69,70],{},"$ref"," chains without collapsing into noise. Nothing executes, so nothing needs CORS, nothing burns quota, and nothing leaks a key into a page you control.",[14,73,74],{},"That single difference cascades into everything else. Swagger UI has to ship an HTTP client, an OAuth2 flow handler, a request builder, and a response renderer, so its bundle is bigger and its DOM is heavier. ReDoc only has to render, so it can virtualize aggressively and stay smooth on a spec that would make Swagger UI's accordion crawl. Pick based on which job your reader is doing, not on which page looks nicer in a screenshot.",[76,77,85,86,85,90,85,94,85,101,85,120,85,128,85,138,85,144,85,153,85,158,85,163,85,169,85,172,85,176,85,180,85,184,85,188,85,192,85,196,85,199,85,203,85,207,85,209,85,212,85,214,85,216,85,219,85,222,85,224,85,227,85,230],"svg",{"viewBox":78,"role":79,"ariaLabelledBy":80,"xmlns":83,"style":84},"0 0 760 300","img",[81,82],"rvs-jobs-t","rvs-jobs-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;height:auto;margin:1.5rem 0;font-family:var(--font-sans);","\n  ",[87,88,89],"title",{"id":81},"Execution surface versus reading surface",[91,92,93],"desc",{"id":82},"Swagger UI loads the spec then fires real authenticated HTTP requests at the production API, while ReDoc loads the spec, renders a reference, and emits copyable samples without any network call.",[95,96],"rect",{"x":97,"y":97,"width":98,"height":99,"fill":100},"0","760","300","var(--c-surface)",[102,103,104,105,85],"defs",{},"\n    ",[106,107,114,115,104],"marker",{"id":108,"viewBox":109,"refX":110,"refY":111,"markerWidth":112,"markerHeight":112,"orient":113},"rvs-jobs-arrow","0 0 10 10","9","5","7","auto-start-reverse","\n      ",[116,117],"path",{"d":118,"fill":119},"M0 0 L10 5 L0 10 z","var(--c-text-muted)",[121,122,127],"text",{"x":123,"y":124,"fill":125,"style":126},"380","30","var(--c-text)","text-anchor:middle;font-size:14;font-family:var(--font-sans);","What happens after the page loads",[95,129],{"x":130,"y":131,"width":132,"height":133,"rx":134,"fill":135,"stroke":136,"style":137},"16","52","352","228","12","var(--c-surface-alt)","var(--c-border)","stroke-width:1.5;",[121,139,143],{"x":140,"y":141,"fill":125,"style":142},"192","78","text-anchor:middle;font-size:13;font-family:var(--font-sans);","Swagger UI: executes",[95,145],{"x":146,"y":147,"width":148,"height":149,"rx":150,"fill":100,"stroke":151,"style":152},"48","94","288","46","8","var(--c-coral)","stroke-width:2;",[121,154,157],{"x":140,"y":155,"fill":125,"style":156},"114","text-anchor:middle;font-size:12;font-family:var(--font-sans);","Docs page in browser",[121,159,162],{"x":140,"y":160,"fill":119,"style":161},"131","text-anchor:middle;font-size:11;font-family:var(--font-sans);","loads spec plus UI bundle",[164,165],"line",{"x1":140,"y1":166,"x2":140,"y2":167,"stroke":119,"style":168},"140","154","stroke-width:2;marker-end:url(#rvs-jobs-arrow);",[95,170],{"x":146,"y":171,"width":148,"height":149,"rx":150,"fill":100,"stroke":151,"style":152},"156",[121,173,175],{"x":140,"y":174,"fill":125,"style":156},"176","Try it out fires real HTTP",[121,177,179],{"x":140,"y":178,"fill":119,"style":161},"193","customer key sits in the tab",[164,181],{"x1":140,"y1":182,"x2":140,"y2":183,"stroke":119,"style":168},"202","216",[95,185],{"x":146,"y":186,"width":148,"height":149,"rx":150,"fill":100,"stroke":187,"style":152},"218","var(--c-blue)",[121,189,191],{"x":140,"y":190,"fill":125,"style":156},"238","Your production API",[121,193,195],{"x":140,"y":194,"fill":119,"style":161},"255","CORS, rate limit, billed call",[95,197],{"x":198,"y":131,"width":132,"height":133,"rx":134,"fill":135,"stroke":136,"style":137},"392",[121,200,202],{"x":201,"y":141,"fill":125,"style":142},"568","ReDoc: renders only",[95,204],{"x":205,"y":147,"width":148,"height":149,"rx":150,"fill":100,"stroke":206,"style":152},"424","var(--c-teal)",[121,208,157],{"x":201,"y":155,"fill":125,"style":156},[121,210,211],{"x":201,"y":160,"fill":119,"style":161},"loads spec plus JS bundle",[164,213],{"x1":201,"y1":166,"x2":201,"y2":167,"stroke":119,"style":168},[95,215],{"x":205,"y":171,"width":148,"height":149,"rx":150,"fill":100,"stroke":206,"style":152},[121,217,218],{"x":201,"y":174,"fill":125,"style":156},"Three-pane reference",[121,220,221],{"x":201,"y":178,"fill":119,"style":161},"virtualized, zero requests",[164,223],{"x1":201,"y1":182,"x2":201,"y2":183,"stroke":119,"style":168},[95,225],{"x":205,"y":186,"width":148,"height":149,"rx":150,"fill":100,"stroke":226,"style":152},"var(--c-yellow)",[121,228,229],{"x":201,"y":190,"fill":125,"style":156},"Copyable curl sample",[121,231,232],{"x":201,"y":194,"fill":119,"style":161},"reader runs it in a terminal",[45,234,236],{"id":235},"the-side-by-side-that-actually-matters","The side-by-side that actually matters",[14,238,239],{},"Feature matrices for docs renderers usually list twenty rows nobody weighs. Five rows decide this.",[241,242,243,259],"table",{},[244,245,246],"thead",{},[247,248,249,253,256],"tr",{},[250,251,252],"th",{},"Dimension",[250,254,255],{},"Swagger UI",[250,257,258],{},"ReDoc",[260,261,262,274,285,296,307],"tbody",{},[247,263,264,268,271],{},[265,266,267],"td",{},"Live try-it-out",[265,269,270],{},"Built in",[265,272,273],{},"None",[247,275,276,279,282],{},[265,277,278],{},"Long-spec readability",[265,280,281],{},"Degrades",[265,283,284],{},"Virtualized panes",[247,286,287,290,293],{},[265,288,289],{},"First load, gzipped",[265,291,292],{},"~350 KB",[265,294,295],{},"~310 KB",[247,297,298,301,304],{},[265,299,300],{},"Theming",[265,302,303],{},"CSS overrides",[265,305,306],{},"Options object",[247,308,309,312,315],{},[265,310,311],{},"OAuth2 flow in page",[265,313,314],{},"Yes",[265,316,317],{},"No",[14,319,320],{},"Bundle weight is close enough that it should not decide anything on its own. Swagger UI ships roughly 330 KB of gzipped JavaScript plus a 20 KB stylesheet; ReDoc's standalone build lands near 310 KB with styles inlined, and jumps past Swagger UI if you leave FastAPI's Google Fonts loading enabled. Both are static assets you can cache for a year at the edge, so the marginal serving cost of either is effectively zero once you stop proxying them through your Python process.",[14,322,323],{},"What differs is what happens after parse. Swagger UI builds DOM for every operation in the accordion. On a spec with two hundred operations that is a slow first paint and a janky scroll on mid-range phones. ReDoc virtualizes the middle pane and keeps a searchable index in the sidebar, so it stays responsive at spec sizes where Swagger UI starts to feel broken. If your surface is under thirty operations you will never notice; if you are heading toward a broad public API, ReDoc wins the readability axis outright.",[76,325,85,330,85,333,85,336,85,340,85,344,85,348,85,353,85,357,85,360,85,363,85,366,85,369,85,372,85,374,85,376,85,379,85,385,85,389,85,395,85,399,85,403,85,407,85,411,85,415,85,420,85,424,85,427,85,431],{"viewBox":326,"role":79,"ariaLabelledBy":327,"xmlns":83,"style":84},"0 0 720 320",[328,329],"rvs-weight-t","rvs-weight-d",[87,331,332],{"id":328},"Approximate first-load transfer weight of each docs renderer",[91,334,335],{"id":329},"Horizontal bars comparing gzipped first-load transfer: Swagger UI around 350 kilobytes, ReDoc around 310, ReDoc with web fonts around 372, and a 200-operation OpenAPI document around 48.",[95,337],{"x":97,"y":97,"width":338,"height":339,"fill":100},"720","320",[121,341,343],{"x":342,"y":124,"fill":125,"style":126},"360","First-load transfer, gzipped",[121,345,347],{"x":342,"y":346,"fill":119,"style":161},"50","approximate, varies by release",[164,349],{"x1":350,"y1":351,"x2":350,"y2":352,"stroke":136,"style":137},"196","60","252",[164,354],{"x1":355,"y1":351,"x2":355,"y2":352,"stroke":136,"style":356},"311","stroke-width:1;",[164,358],{"x1":359,"y1":351,"x2":359,"y2":352,"stroke":136,"style":356},"426",[164,361],{"x1":362,"y1":351,"x2":362,"y2":352,"stroke":136,"style":356},"541",[164,364],{"x1":365,"y1":351,"x2":365,"y2":352,"stroke":136,"style":356},"656",[121,367,97],{"x":350,"y":368,"fill":119,"style":161},"270",[121,370,371],{"x":355,"y":368,"fill":119,"style":161},"100",[121,373,26],{"x":359,"y":368,"fill":119,"style":161},[121,375,99],{"x":362,"y":368,"fill":119,"style":161},[121,377,378],{"x":365,"y":368,"fill":119,"style":161},"400 KB",[121,380,384],{"x":381,"y":382,"fill":125,"style":383},"182","90","text-anchor:end;font-size:12;font-family:var(--font-sans);","Swagger UI (JS+CSS)",[95,386],{"x":350,"y":387,"width":388,"height":124,"rx":111,"fill":100,"stroke":151,"style":152},"72","403",[121,390,394],{"x":391,"y":392,"fill":125,"style":393},"609","92","text-anchor:start;font-size:12;font-family:var(--font-sans);","350",[121,396,398],{"x":381,"y":397,"fill":125,"style":383},"138","ReDoc standalone",[95,400],{"x":350,"y":401,"width":402,"height":124,"rx":111,"fill":100,"stroke":206,"style":152},"120","357",[121,404,406],{"x":405,"y":166,"fill":125,"style":393},"563","310",[121,408,410],{"x":381,"y":409,"fill":125,"style":383},"186","ReDoc + web fonts",[95,412],{"x":350,"y":413,"width":414,"height":124,"rx":111,"fill":100,"stroke":226,"style":152},"168","428",[121,416,419],{"x":417,"y":418,"fill":125,"style":393},"634","188","372",[121,421,423],{"x":381,"y":422,"fill":125,"style":383},"234","Spec, 200 ops",[95,425],{"x":350,"y":183,"width":426,"height":124,"rx":111,"fill":100,"stroke":187,"style":152},"55",[121,428,146],{"x":429,"y":430,"fill":125,"style":393},"261","236",[121,432,434],{"x":342,"y":433,"fill":119,"style":161},"298","Cache the bundles for a year; only the spec changes per deploy.",[45,436,438],{"id":437},"serving-both-from-one-fastapi-app","Serving both from one FastAPI app",[14,440,441,442,39,444,446],{},"Stop using FastAPI's built-in ",[24,443,38],{},[24,445,42],{}," routes in production. They hardcode a public CDN, they cannot be gated, and they give you no place to inject brand assets. Turn them off in the constructor and register your own routes. Driving the choice from an environment variable also lets you ship try-it-out in staging and lock it down in production without a code change.",[448,449,454],"pre",{"className":450,"code":451,"language":452,"meta":453,"style":453},"language-python shiki shiki-themes github-light github-dark","# app\u002Fdocs.py\nimport os\n\nfrom fastapi import Depends, FastAPI\nfrom fastapi.openapi.docs import get_swagger_ui_html\n\nfrom app.docs_redoc import mount_redoc  # defined in the next snippet\n\nAPI_TITLE = os.getenv(\"API_TITLE\", \"Forecast API\")\nDOCS_MODE = os.getenv(\"DOCS_MODE\", \"both\")\nASSET_BASE = os.getenv(\"DOCS_ASSET_BASE\", \"\u002Fstatic\u002Fdocs\")\nSWAGGER_PATH = os.getenv(\"SWAGGER_PATH\", \"\u002Fdocs\")\n\napp = FastAPI(\n    title=API_TITLE,\n    docs_url=None,\n    redoc_url=None,\n    openapi_url=os.getenv(\"OPENAPI_PATH\", \"\u002Fopenapi.json\"),\n)\n\nSWAGGER_PARAMS = {\n    \"defaultModelsExpandDepth\": -1,\n    \"docExpansion\": os.getenv(\"SWAGGER_DOC_EXPANSION\", \"none\"),\n    \"persistAuthorization\": True,\n    \"displayRequestDuration\": True,\n    \"tryItOutEnabled\": os.getenv(\"SWAGGER_TRY_IT_OUT\", \"true\") == \"true\",\n}\n\n\ndef _mount_swagger(target: FastAPI) -> None:\n    @target.get(SWAGGER_PATH, include_in_schema=False)\n    async def swagger_ui(_: None = Depends(require_docs_viewer)):\n        return get_swagger_ui_html(\n            openapi_url=target.openapi_url or \"\u002Fopenapi.json\",\n            title=f\"{API_TITLE} playground\",\n            swagger_js_url=f\"{ASSET_BASE}\u002Fswagger-ui-bundle.js\",\n            swagger_css_url=f\"{ASSET_BASE}\u002Fswagger-ui.css\",\n            swagger_favicon_url=f\"{ASSET_BASE}\u002Ffavicon.png\",\n            swagger_ui_parameters=SWAGGER_PARAMS,\n        )\n\n\ndef register_docs(target: FastAPI) -> None:\n    match DOCS_MODE:\n        case \"swagger\":\n            _mount_swagger(target)\n        case \"redoc\":\n            mount_redoc(target)\n        case \"both\":\n            _mount_swagger(target)\n            mount_redoc(target)\n        case \"off\":\n            return\n        case unknown:\n            raise ValueError(f\"unsupported DOCS_MODE: {unknown!r}\")\n","python","",[24,455,456,464,475,482,496,509,514,530,535,561,581,601,621,626,638,652,664,676,698,703,708,719,736,755,768,780,807,813,818,823,841,864,886,895,914,936,956,975,994,1006,1012,1017,1022,1036,1047,1058,1064,1074,1080,1090,1095,1100,1110,1116,1124],{"__ignoreMap":453},[457,458,460],"span",{"class":164,"line":459},1,[457,461,463],{"class":462},"sJ8bj","# app\u002Fdocs.py\n",[457,465,467,471],{"class":164,"line":466},2,[457,468,470],{"class":469},"szBVR","import",[457,472,474],{"class":473},"sVt8B"," os\n",[457,476,478],{"class":164,"line":477},3,[457,479,481],{"emptyLinePlaceholder":480},true,"\n",[457,483,485,488,491,493],{"class":164,"line":484},4,[457,486,487],{"class":469},"from",[457,489,490],{"class":473}," fastapi ",[457,492,470],{"class":469},[457,494,495],{"class":473}," Depends, FastAPI\n",[457,497,499,501,504,506],{"class":164,"line":498},5,[457,500,487],{"class":469},[457,502,503],{"class":473}," fastapi.openapi.docs ",[457,505,470],{"class":469},[457,507,508],{"class":473}," get_swagger_ui_html\n",[457,510,512],{"class":164,"line":511},6,[457,513,481],{"emptyLinePlaceholder":480},[457,515,517,519,522,524,527],{"class":164,"line":516},7,[457,518,487],{"class":469},[457,520,521],{"class":473}," app.docs_redoc ",[457,523,470],{"class":469},[457,525,526],{"class":473}," mount_redoc  ",[457,528,529],{"class":462},"# defined in the next snippet\n",[457,531,533],{"class":164,"line":532},8,[457,534,481],{"emptyLinePlaceholder":480},[457,536,538,542,545,548,552,555,558],{"class":164,"line":537},9,[457,539,541],{"class":540},"sj4cs","API_TITLE",[457,543,544],{"class":469}," =",[457,546,547],{"class":473}," os.getenv(",[457,549,551],{"class":550},"sZZnC","\"API_TITLE\"",[457,553,554],{"class":473},", ",[457,556,557],{"class":550},"\"Forecast API\"",[457,559,560],{"class":473},")\n",[457,562,564,567,569,571,574,576,579],{"class":164,"line":563},10,[457,565,566],{"class":540},"DOCS_MODE",[457,568,544],{"class":469},[457,570,547],{"class":473},[457,572,573],{"class":550},"\"DOCS_MODE\"",[457,575,554],{"class":473},[457,577,578],{"class":550},"\"both\"",[457,580,560],{"class":473},[457,582,584,587,589,591,594,596,599],{"class":164,"line":583},11,[457,585,586],{"class":540},"ASSET_BASE",[457,588,544],{"class":469},[457,590,547],{"class":473},[457,592,593],{"class":550},"\"DOCS_ASSET_BASE\"",[457,595,554],{"class":473},[457,597,598],{"class":550},"\"\u002Fstatic\u002Fdocs\"",[457,600,560],{"class":473},[457,602,604,607,609,611,614,616,619],{"class":164,"line":603},12,[457,605,606],{"class":540},"SWAGGER_PATH",[457,608,544],{"class":469},[457,610,547],{"class":473},[457,612,613],{"class":550},"\"SWAGGER_PATH\"",[457,615,554],{"class":473},[457,617,618],{"class":550},"\"\u002Fdocs\"",[457,620,560],{"class":473},[457,622,624],{"class":164,"line":623},13,[457,625,481],{"emptyLinePlaceholder":480},[457,627,629,632,635],{"class":164,"line":628},14,[457,630,631],{"class":473},"app ",[457,633,634],{"class":469},"=",[457,636,637],{"class":473}," FastAPI(\n",[457,639,641,645,647,649],{"class":164,"line":640},15,[457,642,644],{"class":643},"s4XuR","    title",[457,646,634],{"class":469},[457,648,541],{"class":540},[457,650,651],{"class":473},",\n",[457,653,655,658,660,662],{"class":164,"line":654},16,[457,656,657],{"class":643},"    docs_url",[457,659,634],{"class":469},[457,661,273],{"class":540},[457,663,651],{"class":473},[457,665,667,670,672,674],{"class":164,"line":666},17,[457,668,669],{"class":643},"    redoc_url",[457,671,634],{"class":469},[457,673,273],{"class":540},[457,675,651],{"class":473},[457,677,679,682,684,687,690,692,695],{"class":164,"line":678},18,[457,680,681],{"class":643},"    openapi_url",[457,683,634],{"class":469},[457,685,686],{"class":473},"os.getenv(",[457,688,689],{"class":550},"\"OPENAPI_PATH\"",[457,691,554],{"class":473},[457,693,694],{"class":550},"\"\u002Fopenapi.json\"",[457,696,697],{"class":473},"),\n",[457,699,701],{"class":164,"line":700},19,[457,702,560],{"class":473},[457,704,706],{"class":164,"line":705},20,[457,707,481],{"emptyLinePlaceholder":480},[457,709,711,714,716],{"class":164,"line":710},21,[457,712,713],{"class":540},"SWAGGER_PARAMS",[457,715,544],{"class":469},[457,717,718],{"class":473}," {\n",[457,720,722,725,728,731,734],{"class":164,"line":721},22,[457,723,724],{"class":550},"    \"defaultModelsExpandDepth\"",[457,726,727],{"class":473},": ",[457,729,730],{"class":469},"-",[457,732,733],{"class":540},"1",[457,735,651],{"class":473},[457,737,739,742,745,748,750,753],{"class":164,"line":738},23,[457,740,741],{"class":550},"    \"docExpansion\"",[457,743,744],{"class":473},": os.getenv(",[457,746,747],{"class":550},"\"SWAGGER_DOC_EXPANSION\"",[457,749,554],{"class":473},[457,751,752],{"class":550},"\"none\"",[457,754,697],{"class":473},[457,756,758,761,763,766],{"class":164,"line":757},24,[457,759,760],{"class":550},"    \"persistAuthorization\"",[457,762,727],{"class":473},[457,764,765],{"class":540},"True",[457,767,651],{"class":473},[457,769,771,774,776,778],{"class":164,"line":770},25,[457,772,773],{"class":550},"    \"displayRequestDuration\"",[457,775,727],{"class":473},[457,777,765],{"class":540},[457,779,651],{"class":473},[457,781,783,786,788,791,793,796,799,802,805],{"class":164,"line":782},26,[457,784,785],{"class":550},"    \"tryItOutEnabled\"",[457,787,744],{"class":473},[457,789,790],{"class":550},"\"SWAGGER_TRY_IT_OUT\"",[457,792,554],{"class":473},[457,794,795],{"class":550},"\"true\"",[457,797,798],{"class":473},") ",[457,800,801],{"class":469},"==",[457,803,804],{"class":550}," \"true\"",[457,806,651],{"class":473},[457,808,810],{"class":164,"line":809},27,[457,811,812],{"class":473},"}\n",[457,814,816],{"class":164,"line":815},28,[457,817,481],{"emptyLinePlaceholder":480},[457,819,821],{"class":164,"line":820},29,[457,822,481],{"emptyLinePlaceholder":480},[457,824,826,829,833,836,838],{"class":164,"line":825},30,[457,827,828],{"class":469},"def",[457,830,832],{"class":831},"sScJk"," _mount_swagger",[457,834,835],{"class":473},"(target: FastAPI) -> ",[457,837,273],{"class":540},[457,839,840],{"class":473},":\n",[457,842,844,847,850,852,854,857,859,862],{"class":164,"line":843},31,[457,845,846],{"class":831},"    @target.get",[457,848,849],{"class":473},"(",[457,851,606],{"class":540},[457,853,554],{"class":473},[457,855,856],{"class":643},"include_in_schema",[457,858,634],{"class":469},[457,860,861],{"class":540},"False",[457,863,560],{"class":473},[457,865,867,870,873,876,879,881,883],{"class":164,"line":866},32,[457,868,869],{"class":469},"    async",[457,871,872],{"class":469}," def",[457,874,875],{"class":831}," swagger_ui",[457,877,878],{"class":473},"(_: ",[457,880,273],{"class":540},[457,882,544],{"class":469},[457,884,885],{"class":473}," Depends(require_docs_viewer)):\n",[457,887,889,892],{"class":164,"line":888},33,[457,890,891],{"class":469},"        return",[457,893,894],{"class":473}," get_swagger_ui_html(\n",[457,896,898,901,903,906,909,912],{"class":164,"line":897},34,[457,899,900],{"class":643},"            openapi_url",[457,902,634],{"class":469},[457,904,905],{"class":473},"target.openapi_url ",[457,907,908],{"class":469},"or",[457,910,911],{"class":550}," \"\u002Fopenapi.json\"",[457,913,651],{"class":473},[457,915,917,920,922,925,928,931,934],{"class":164,"line":916},35,[457,918,919],{"class":643},"            title",[457,921,634],{"class":469},[457,923,924],{"class":469},"f",[457,926,927],{"class":550},"\"",[457,929,930],{"class":540},"{API_TITLE}",[457,932,933],{"class":550}," playground\"",[457,935,651],{"class":473},[457,937,939,942,944,946,948,951,954],{"class":164,"line":938},36,[457,940,941],{"class":643},"            swagger_js_url",[457,943,634],{"class":469},[457,945,924],{"class":469},[457,947,927],{"class":550},[457,949,950],{"class":540},"{ASSET_BASE}",[457,952,953],{"class":550},"\u002Fswagger-ui-bundle.js\"",[457,955,651],{"class":473},[457,957,959,962,964,966,968,970,973],{"class":164,"line":958},37,[457,960,961],{"class":643},"            swagger_css_url",[457,963,634],{"class":469},[457,965,924],{"class":469},[457,967,927],{"class":550},[457,969,950],{"class":540},[457,971,972],{"class":550},"\u002Fswagger-ui.css\"",[457,974,651],{"class":473},[457,976,978,981,983,985,987,989,992],{"class":164,"line":977},38,[457,979,980],{"class":643},"            swagger_favicon_url",[457,982,634],{"class":469},[457,984,924],{"class":469},[457,986,927],{"class":550},[457,988,950],{"class":540},[457,990,991],{"class":550},"\u002Ffavicon.png\"",[457,993,651],{"class":473},[457,995,997,1000,1002,1004],{"class":164,"line":996},39,[457,998,999],{"class":643},"            swagger_ui_parameters",[457,1001,634],{"class":469},[457,1003,713],{"class":540},[457,1005,651],{"class":473},[457,1007,1009],{"class":164,"line":1008},40,[457,1010,1011],{"class":473},"        )\n",[457,1013,1015],{"class":164,"line":1014},41,[457,1016,481],{"emptyLinePlaceholder":480},[457,1018,1020],{"class":164,"line":1019},42,[457,1021,481],{"emptyLinePlaceholder":480},[457,1023,1025,1027,1030,1032,1034],{"class":164,"line":1024},43,[457,1026,828],{"class":469},[457,1028,1029],{"class":831}," register_docs",[457,1031,835],{"class":473},[457,1033,273],{"class":540},[457,1035,840],{"class":473},[457,1037,1039,1042,1045],{"class":164,"line":1038},44,[457,1040,1041],{"class":469},"    match",[457,1043,1044],{"class":540}," DOCS_MODE",[457,1046,840],{"class":473},[457,1048,1050,1053,1056],{"class":164,"line":1049},45,[457,1051,1052],{"class":469},"        case",[457,1054,1055],{"class":550}," \"swagger\"",[457,1057,840],{"class":473},[457,1059,1061],{"class":164,"line":1060},46,[457,1062,1063],{"class":473},"            _mount_swagger(target)\n",[457,1065,1067,1069,1072],{"class":164,"line":1066},47,[457,1068,1052],{"class":469},[457,1070,1071],{"class":550}," \"redoc\"",[457,1073,840],{"class":473},[457,1075,1077],{"class":164,"line":1076},48,[457,1078,1079],{"class":473},"            mount_redoc(target)\n",[457,1081,1083,1085,1088],{"class":164,"line":1082},49,[457,1084,1052],{"class":469},[457,1086,1087],{"class":550}," \"both\"",[457,1089,840],{"class":473},[457,1091,1093],{"class":164,"line":1092},50,[457,1094,1063],{"class":473},[457,1096,1098],{"class":164,"line":1097},51,[457,1099,1079],{"class":473},[457,1101,1103,1105,1108],{"class":164,"line":1102},52,[457,1104,1052],{"class":469},[457,1106,1107],{"class":550}," \"off\"",[457,1109,840],{"class":473},[457,1111,1113],{"class":164,"line":1112},53,[457,1114,1115],{"class":469},"            return\n",[457,1117,1119,1121],{"class":164,"line":1118},54,[457,1120,1052],{"class":469},[457,1122,1123],{"class":473}," unknown:\n",[457,1125,1127,1130,1133,1135,1137,1140,1143,1146,1149,1152,1154],{"class":164,"line":1126},55,[457,1128,1129],{"class":469},"            raise",[457,1131,1132],{"class":540}," ValueError",[457,1134,849],{"class":473},[457,1136,924],{"class":469},[457,1138,1139],{"class":550},"\"unsupported DOCS_MODE: ",[457,1141,1142],{"class":540},"{",[457,1144,1145],{"class":473},"unknown",[457,1147,1148],{"class":469},"!r",[457,1150,1151],{"class":540},"}",[457,1153,927],{"class":550},[457,1155,560],{"class":473},[14,1157,1158,1161,1162,1165,1166,1169],{},[24,1159,1160],{},"require_docs_viewer"," is your own dependency. In production it should check a session cookie or a signed link; in development it returns immediately. ",[24,1163,1164],{},"defaultModelsExpandDepth: -1"," removes the giant schema dump Swagger UI appends below every page, which is the single fastest readability win available. ",[24,1167,1168],{},"docExpansion: \"none\""," collapses the operation list so a fifty-endpoint API opens as a scannable index instead of a wall.",[14,1171,1172],{},"The ReDoc half is where you get real control, because ReDoc takes a JavaScript options object rather than a stylesheet. Build the HTML yourself and feed it a theme sourced from environment variables so staging, production, and a white-labelled partner deployment all render from one template.",[448,1174,1176],{"className":450,"code":1175,"language":452,"meta":453,"style":453},"# app\u002Fdocs_redoc.py\nimport json\nimport os\nfrom string import Template\n\nfrom fastapi import FastAPI\nfrom fastapi.responses import HTMLResponse\n\nASSET_BASE = os.getenv(\"DOCS_ASSET_BASE\", \"\u002Fstatic\u002Fdocs\")\nREDOC_PATH = os.getenv(\"REDOC_PATH\", \"\u002Freference\")\n\nREDOC_OPTIONS = {\n    \"theme\": {\n        \"colors\": {\"primary\": {\"main\": os.getenv(\"BRAND_PRIMARY\", \"#2563eb\")}},\n        \"typography\": {\n            \"fontFamily\": os.getenv(\"BRAND_FONT\", \"system-ui, sans-serif\"),\n            \"code\": {\"fontFamily\": os.getenv(\"BRAND_MONO\", \"ui-monospace, monospace\")},\n        },\n    },\n    \"expandResponses\": os.getenv(\"REDOC_EXPAND_RESPONSES\", \"200,201\"),\n    \"hideDownloadButton\": os.getenv(\"REDOC_HIDE_DOWNLOAD\", \"false\") == \"true\",\n    \"requiredPropsFirst\": True,\n    \"sortTagsAlphabetically\": True,\n}\n\n_PAGE = Template(\n    \"\"\"\u003C!doctype html>\n\u003Chtml>\u003Chead>\u003Cmeta charset=\"utf-8\">\u003Ctitle>$title\u003C\u002Ftitle>\n\u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n\u003Cstyle>body { margin: 0; padding: 0; }\u003C\u002Fstyle>\n\u003C\u002Fhead>\u003Cbody>\n\u003Cdiv id=\"redoc\">\u003C\u002Fdiv>\n\u003Cscript src=\"$js_url\">\u003C\u002Fscript>\n\u003Cscript>Redoc.init($spec_url, $options, document.getElementById(\"redoc\"));\u003C\u002Fscript>\n\u003C\u002Fbody>\u003C\u002Fhtml>\"\"\"\n)\n\n\ndef mount_redoc(target: FastAPI) -> None:\n    @target.get(REDOC_PATH, include_in_schema=False)\n    async def reference() -> HTMLResponse:\n        html = _PAGE.substitute(\n            title=f\"{os.getenv('API_TITLE', 'API')} reference\",\n            js_url=f\"{ASSET_BASE}\u002Fredoc.standalone.js\",\n            spec_url=json.dumps(target.openapi_url or \"\u002Fopenapi.json\"),\n            options=json.dumps(REDOC_OPTIONS),\n        )\n        return HTMLResponse(html, headers={\"Cache-Control\": \"public, max-age=300\"})\n",[24,1177,1178,1183,1190,1196,1208,1212,1223,1235,1239,1255,1274,1278,1287,1295,1324,1331,1348,1371,1376,1381,1398,1421,1432,1443,1447,1451,1461,1466,1471,1476,1481,1486,1491,1496,1501,1506,1510,1514,1518,1531,1549,1561,1574,1606,1624,1640,1654,1658],{"__ignoreMap":453},[457,1179,1180],{"class":164,"line":459},[457,1181,1182],{"class":462},"# app\u002Fdocs_redoc.py\n",[457,1184,1185,1187],{"class":164,"line":466},[457,1186,470],{"class":469},[457,1188,1189],{"class":473}," json\n",[457,1191,1192,1194],{"class":164,"line":477},[457,1193,470],{"class":469},[457,1195,474],{"class":473},[457,1197,1198,1200,1203,1205],{"class":164,"line":484},[457,1199,487],{"class":469},[457,1201,1202],{"class":473}," string ",[457,1204,470],{"class":469},[457,1206,1207],{"class":473}," Template\n",[457,1209,1210],{"class":164,"line":498},[457,1211,481],{"emptyLinePlaceholder":480},[457,1213,1214,1216,1218,1220],{"class":164,"line":511},[457,1215,487],{"class":469},[457,1217,490],{"class":473},[457,1219,470],{"class":469},[457,1221,1222],{"class":473}," FastAPI\n",[457,1224,1225,1227,1230,1232],{"class":164,"line":516},[457,1226,487],{"class":469},[457,1228,1229],{"class":473}," fastapi.responses ",[457,1231,470],{"class":469},[457,1233,1234],{"class":473}," HTMLResponse\n",[457,1236,1237],{"class":164,"line":532},[457,1238,481],{"emptyLinePlaceholder":480},[457,1240,1241,1243,1245,1247,1249,1251,1253],{"class":164,"line":537},[457,1242,586],{"class":540},[457,1244,544],{"class":469},[457,1246,547],{"class":473},[457,1248,593],{"class":550},[457,1250,554],{"class":473},[457,1252,598],{"class":550},[457,1254,560],{"class":473},[457,1256,1257,1260,1262,1264,1267,1269,1272],{"class":164,"line":563},[457,1258,1259],{"class":540},"REDOC_PATH",[457,1261,544],{"class":469},[457,1263,547],{"class":473},[457,1265,1266],{"class":550},"\"REDOC_PATH\"",[457,1268,554],{"class":473},[457,1270,1271],{"class":550},"\"\u002Freference\"",[457,1273,560],{"class":473},[457,1275,1276],{"class":164,"line":583},[457,1277,481],{"emptyLinePlaceholder":480},[457,1279,1280,1283,1285],{"class":164,"line":603},[457,1281,1282],{"class":540},"REDOC_OPTIONS",[457,1284,544],{"class":469},[457,1286,718],{"class":473},[457,1288,1289,1292],{"class":164,"line":623},[457,1290,1291],{"class":550},"    \"theme\"",[457,1293,1294],{"class":473},": {\n",[457,1296,1297,1300,1303,1306,1308,1311,1313,1316,1318,1321],{"class":164,"line":628},[457,1298,1299],{"class":550},"        \"colors\"",[457,1301,1302],{"class":473},": {",[457,1304,1305],{"class":550},"\"primary\"",[457,1307,1302],{"class":473},[457,1309,1310],{"class":550},"\"main\"",[457,1312,744],{"class":473},[457,1314,1315],{"class":550},"\"BRAND_PRIMARY\"",[457,1317,554],{"class":473},[457,1319,1320],{"class":550},"\"#2563eb\"",[457,1322,1323],{"class":473},")}},\n",[457,1325,1326,1329],{"class":164,"line":640},[457,1327,1328],{"class":550},"        \"typography\"",[457,1330,1294],{"class":473},[457,1332,1333,1336,1338,1341,1343,1346],{"class":164,"line":654},[457,1334,1335],{"class":550},"            \"fontFamily\"",[457,1337,744],{"class":473},[457,1339,1340],{"class":550},"\"BRAND_FONT\"",[457,1342,554],{"class":473},[457,1344,1345],{"class":550},"\"system-ui, sans-serif\"",[457,1347,697],{"class":473},[457,1349,1350,1353,1355,1358,1360,1363,1365,1368],{"class":164,"line":666},[457,1351,1352],{"class":550},"            \"code\"",[457,1354,1302],{"class":473},[457,1356,1357],{"class":550},"\"fontFamily\"",[457,1359,744],{"class":473},[457,1361,1362],{"class":550},"\"BRAND_MONO\"",[457,1364,554],{"class":473},[457,1366,1367],{"class":550},"\"ui-monospace, monospace\"",[457,1369,1370],{"class":473},")},\n",[457,1372,1373],{"class":164,"line":678},[457,1374,1375],{"class":473},"        },\n",[457,1377,1378],{"class":164,"line":700},[457,1379,1380],{"class":473},"    },\n",[457,1382,1383,1386,1388,1391,1393,1396],{"class":164,"line":705},[457,1384,1385],{"class":550},"    \"expandResponses\"",[457,1387,744],{"class":473},[457,1389,1390],{"class":550},"\"REDOC_EXPAND_RESPONSES\"",[457,1392,554],{"class":473},[457,1394,1395],{"class":550},"\"200,201\"",[457,1397,697],{"class":473},[457,1399,1400,1403,1405,1408,1410,1413,1415,1417,1419],{"class":164,"line":710},[457,1401,1402],{"class":550},"    \"hideDownloadButton\"",[457,1404,744],{"class":473},[457,1406,1407],{"class":550},"\"REDOC_HIDE_DOWNLOAD\"",[457,1409,554],{"class":473},[457,1411,1412],{"class":550},"\"false\"",[457,1414,798],{"class":473},[457,1416,801],{"class":469},[457,1418,804],{"class":550},[457,1420,651],{"class":473},[457,1422,1423,1426,1428,1430],{"class":164,"line":721},[457,1424,1425],{"class":550},"    \"requiredPropsFirst\"",[457,1427,727],{"class":473},[457,1429,765],{"class":540},[457,1431,651],{"class":473},[457,1433,1434,1437,1439,1441],{"class":164,"line":738},[457,1435,1436],{"class":550},"    \"sortTagsAlphabetically\"",[457,1438,727],{"class":473},[457,1440,765],{"class":540},[457,1442,651],{"class":473},[457,1444,1445],{"class":164,"line":757},[457,1446,812],{"class":473},[457,1448,1449],{"class":164,"line":770},[457,1450,481],{"emptyLinePlaceholder":480},[457,1452,1453,1456,1458],{"class":164,"line":782},[457,1454,1455],{"class":540},"_PAGE",[457,1457,544],{"class":469},[457,1459,1460],{"class":473}," Template(\n",[457,1462,1463],{"class":164,"line":809},[457,1464,1465],{"class":550},"    \"\"\"\u003C!doctype html>\n",[457,1467,1468],{"class":164,"line":815},[457,1469,1470],{"class":550},"\u003Chtml>\u003Chead>\u003Cmeta charset=\"utf-8\">\u003Ctitle>$title\u003C\u002Ftitle>\n",[457,1472,1473],{"class":164,"line":820},[457,1474,1475],{"class":550},"\u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n",[457,1477,1478],{"class":164,"line":825},[457,1479,1480],{"class":550},"\u003Cstyle>body { margin: 0; padding: 0; }\u003C\u002Fstyle>\n",[457,1482,1483],{"class":164,"line":843},[457,1484,1485],{"class":550},"\u003C\u002Fhead>\u003Cbody>\n",[457,1487,1488],{"class":164,"line":866},[457,1489,1490],{"class":550},"\u003Cdiv id=\"redoc\">\u003C\u002Fdiv>\n",[457,1492,1493],{"class":164,"line":888},[457,1494,1495],{"class":550},"\u003Cscript src=\"$js_url\">\u003C\u002Fscript>\n",[457,1497,1498],{"class":164,"line":897},[457,1499,1500],{"class":550},"\u003Cscript>Redoc.init($spec_url, $options, document.getElementById(\"redoc\"));\u003C\u002Fscript>\n",[457,1502,1503],{"class":164,"line":916},[457,1504,1505],{"class":550},"\u003C\u002Fbody>\u003C\u002Fhtml>\"\"\"\n",[457,1507,1508],{"class":164,"line":938},[457,1509,560],{"class":473},[457,1511,1512],{"class":164,"line":958},[457,1513,481],{"emptyLinePlaceholder":480},[457,1515,1516],{"class":164,"line":977},[457,1517,481],{"emptyLinePlaceholder":480},[457,1519,1520,1522,1525,1527,1529],{"class":164,"line":996},[457,1521,828],{"class":469},[457,1523,1524],{"class":831}," mount_redoc",[457,1526,835],{"class":473},[457,1528,273],{"class":540},[457,1530,840],{"class":473},[457,1532,1533,1535,1537,1539,1541,1543,1545,1547],{"class":164,"line":1008},[457,1534,846],{"class":831},[457,1536,849],{"class":473},[457,1538,1259],{"class":540},[457,1540,554],{"class":473},[457,1542,856],{"class":643},[457,1544,634],{"class":469},[457,1546,861],{"class":540},[457,1548,560],{"class":473},[457,1550,1551,1553,1555,1558],{"class":164,"line":1014},[457,1552,869],{"class":469},[457,1554,872],{"class":469},[457,1556,1557],{"class":831}," reference",[457,1559,1560],{"class":473},"() -> HTMLResponse:\n",[457,1562,1563,1566,1568,1571],{"class":164,"line":1019},[457,1564,1565],{"class":473},"        html ",[457,1567,634],{"class":469},[457,1569,1570],{"class":540}," _PAGE",[457,1572,1573],{"class":473},".substitute(\n",[457,1575,1576,1578,1580,1582,1584,1586,1588,1591,1593,1596,1599,1601,1604],{"class":164,"line":1024},[457,1577,919],{"class":643},[457,1579,634],{"class":469},[457,1581,924],{"class":469},[457,1583,927],{"class":550},[457,1585,1142],{"class":540},[457,1587,686],{"class":473},[457,1589,1590],{"class":550},"'API_TITLE'",[457,1592,554],{"class":473},[457,1594,1595],{"class":550},"'API'",[457,1597,1598],{"class":473},")",[457,1600,1151],{"class":540},[457,1602,1603],{"class":550}," reference\"",[457,1605,651],{"class":473},[457,1607,1608,1611,1613,1615,1617,1619,1622],{"class":164,"line":1038},[457,1609,1610],{"class":643},"            js_url",[457,1612,634],{"class":469},[457,1614,924],{"class":469},[457,1616,927],{"class":550},[457,1618,950],{"class":540},[457,1620,1621],{"class":550},"\u002Fredoc.standalone.js\"",[457,1623,651],{"class":473},[457,1625,1626,1629,1631,1634,1636,1638],{"class":164,"line":1049},[457,1627,1628],{"class":643},"            spec_url",[457,1630,634],{"class":469},[457,1632,1633],{"class":473},"json.dumps(target.openapi_url ",[457,1635,908],{"class":469},[457,1637,911],{"class":550},[457,1639,697],{"class":473},[457,1641,1642,1645,1647,1650,1652],{"class":164,"line":1060},[457,1643,1644],{"class":643},"            options",[457,1646,634],{"class":469},[457,1648,1649],{"class":473},"json.dumps(",[457,1651,1282],{"class":540},[457,1653,697],{"class":473},[457,1655,1656],{"class":164,"line":1066},[457,1657,1011],{"class":473},[457,1659,1660,1662,1665,1668,1670,1672,1675,1677,1680],{"class":164,"line":1076},[457,1661,891],{"class":469},[457,1663,1664],{"class":473}," HTMLResponse(html, ",[457,1666,1667],{"class":643},"headers",[457,1669,634],{"class":469},[457,1671,1142],{"class":473},[457,1673,1674],{"class":550},"\"Cache-Control\"",[457,1676,727],{"class":473},[457,1678,1679],{"class":550},"\"public, max-age=300\"",[457,1681,1682],{"class":473},"})\n",[14,1684,1685,1686,1688,1689,1692,1693,1697],{},"Two routes, one spec, no duplicated content. ",[24,1687,38],{}," becomes the playground you link from your signup email; ",[24,1690,1691],{},"\u002Freference"," becomes the canonical, linkable documentation you point search engines and enterprise integrators at. Both belong inside the wider surface described in ",[18,1694,1696],{"href":1695},"\u002Fbuilding-monetizing-api-driven-micro-saas\u002Fdeploying-apis-to-render-or-vercel\u002Fcreating-a-developer-portal-for-your-api\u002F","creating a developer portal for your API",".",[45,1699,1701],{"id":1700},"self-host-the-assets-always","Self-host the assets, always",[14,1703,1704],{},"FastAPI's defaults pull both bundles from a public CDN. That is one uptime dependency, one privacy disclosure, and one supply-chain hole you do not need. If that CDN serves a compromised build, your docs page executes attacker code in a tab where your customers just pasted live API keys. Vendor the files at build time, pin them by version in a manifest, and serve them from your own static mount or object storage.",[448,1706,1708],{"className":450,"code":1707,"language":452,"meta":453,"style":453},"# scripts\u002Fvendor_docs_assets.py\nimport asyncio\nimport os\nimport tomllib\nfrom pathlib import Path\n\nimport httpx\n\nMANIFEST = Path(os.getenv(\"DOCS_ASSET_MANIFEST\", \"docs-assets.toml\"))\nTARGET_DIR = Path(os.getenv(\"DOCS_ASSET_DIR\", \"static\u002Fdocs\"))\n\n\nasync def _fetch(client: httpx.AsyncClient, name: str, url: str) -> tuple[str, int]:\n    response = await client.get(url)\n    response.raise_for_status()\n    (TARGET_DIR \u002F name).write_bytes(response.content)\n    return name, len(response.content)\n\n\nasync def main() -> None:\n    manifest = tomllib.loads(MANIFEST.read_text(encoding=\"utf-8\"))\n    TARGET_DIR.mkdir(parents=True, exist_ok=True)\n    timeout = httpx.Timeout(float(os.getenv(\"DOCS_FETCH_TIMEOUT\", \"30\")))\n    async with httpx.AsyncClient(timeout=timeout, follow_redirects=True) as client:\n        results = await asyncio.gather(\n            *(_fetch(client, name, url) for name, url in manifest[\"assets\"].items())\n        )\n    for name, size in sorted(results):\n        print(f\"{name}: {size \u002F 1024:.1f} KiB\")\n\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n",[24,1709,1710,1715,1722,1728,1735,1747,1751,1758,1762,1783,1802,1806,1810,1844,1857,1862,1875,1889,1893,1897,1913,1938,1964,1991,2024,2036,2062,2066,2082,2123,2127,2131,2147],{"__ignoreMap":453},[457,1711,1712],{"class":164,"line":459},[457,1713,1714],{"class":462},"# scripts\u002Fvendor_docs_assets.py\n",[457,1716,1717,1719],{"class":164,"line":466},[457,1718,470],{"class":469},[457,1720,1721],{"class":473}," asyncio\n",[457,1723,1724,1726],{"class":164,"line":477},[457,1725,470],{"class":469},[457,1727,474],{"class":473},[457,1729,1730,1732],{"class":164,"line":484},[457,1731,470],{"class":469},[457,1733,1734],{"class":473}," tomllib\n",[457,1736,1737,1739,1742,1744],{"class":164,"line":498},[457,1738,487],{"class":469},[457,1740,1741],{"class":473}," pathlib ",[457,1743,470],{"class":469},[457,1745,1746],{"class":473}," Path\n",[457,1748,1749],{"class":164,"line":511},[457,1750,481],{"emptyLinePlaceholder":480},[457,1752,1753,1755],{"class":164,"line":516},[457,1754,470],{"class":469},[457,1756,1757],{"class":473}," httpx\n",[457,1759,1760],{"class":164,"line":532},[457,1761,481],{"emptyLinePlaceholder":480},[457,1763,1764,1767,1769,1772,1775,1777,1780],{"class":164,"line":537},[457,1765,1766],{"class":540},"MANIFEST",[457,1768,544],{"class":469},[457,1770,1771],{"class":473}," Path(os.getenv(",[457,1773,1774],{"class":550},"\"DOCS_ASSET_MANIFEST\"",[457,1776,554],{"class":473},[457,1778,1779],{"class":550},"\"docs-assets.toml\"",[457,1781,1782],{"class":473},"))\n",[457,1784,1785,1788,1790,1792,1795,1797,1800],{"class":164,"line":563},[457,1786,1787],{"class":540},"TARGET_DIR",[457,1789,544],{"class":469},[457,1791,1771],{"class":473},[457,1793,1794],{"class":550},"\"DOCS_ASSET_DIR\"",[457,1796,554],{"class":473},[457,1798,1799],{"class":550},"\"static\u002Fdocs\"",[457,1801,1782],{"class":473},[457,1803,1804],{"class":164,"line":583},[457,1805,481],{"emptyLinePlaceholder":480},[457,1807,1808],{"class":164,"line":603},[457,1809,481],{"emptyLinePlaceholder":480},[457,1811,1812,1815,1817,1820,1823,1826,1829,1831,1834,1836,1838,1841],{"class":164,"line":623},[457,1813,1814],{"class":469},"async",[457,1816,872],{"class":469},[457,1818,1819],{"class":831}," _fetch",[457,1821,1822],{"class":473},"(client: httpx.AsyncClient, name: ",[457,1824,1825],{"class":540},"str",[457,1827,1828],{"class":473},", url: ",[457,1830,1825],{"class":540},[457,1832,1833],{"class":473},") -> tuple[",[457,1835,1825],{"class":540},[457,1837,554],{"class":473},[457,1839,1840],{"class":540},"int",[457,1842,1843],{"class":473},"]:\n",[457,1845,1846,1849,1851,1854],{"class":164,"line":628},[457,1847,1848],{"class":473},"    response ",[457,1850,634],{"class":469},[457,1852,1853],{"class":469}," await",[457,1855,1856],{"class":473}," client.get(url)\n",[457,1858,1859],{"class":164,"line":640},[457,1860,1861],{"class":473},"    response.raise_for_status()\n",[457,1863,1864,1867,1869,1872],{"class":164,"line":654},[457,1865,1866],{"class":473},"    (",[457,1868,1787],{"class":540},[457,1870,1871],{"class":469}," \u002F",[457,1873,1874],{"class":473}," name).write_bytes(response.content)\n",[457,1876,1877,1880,1883,1886],{"class":164,"line":666},[457,1878,1879],{"class":469},"    return",[457,1881,1882],{"class":473}," name, ",[457,1884,1885],{"class":540},"len",[457,1887,1888],{"class":473},"(response.content)\n",[457,1890,1891],{"class":164,"line":678},[457,1892,481],{"emptyLinePlaceholder":480},[457,1894,1895],{"class":164,"line":700},[457,1896,481],{"emptyLinePlaceholder":480},[457,1898,1899,1901,1903,1906,1909,1911],{"class":164,"line":705},[457,1900,1814],{"class":469},[457,1902,872],{"class":469},[457,1904,1905],{"class":831}," main",[457,1907,1908],{"class":473},"() -> ",[457,1910,273],{"class":540},[457,1912,840],{"class":473},[457,1914,1915,1918,1920,1923,1925,1928,1931,1933,1936],{"class":164,"line":710},[457,1916,1917],{"class":473},"    manifest ",[457,1919,634],{"class":469},[457,1921,1922],{"class":473}," tomllib.loads(",[457,1924,1766],{"class":540},[457,1926,1927],{"class":473},".read_text(",[457,1929,1930],{"class":643},"encoding",[457,1932,634],{"class":469},[457,1934,1935],{"class":550},"\"utf-8\"",[457,1937,1782],{"class":473},[457,1939,1940,1943,1946,1949,1951,1953,1955,1958,1960,1962],{"class":164,"line":721},[457,1941,1942],{"class":540},"    TARGET_DIR",[457,1944,1945],{"class":473},".mkdir(",[457,1947,1948],{"class":643},"parents",[457,1950,634],{"class":469},[457,1952,765],{"class":540},[457,1954,554],{"class":473},[457,1956,1957],{"class":643},"exist_ok",[457,1959,634],{"class":469},[457,1961,765],{"class":540},[457,1963,560],{"class":473},[457,1965,1966,1969,1971,1974,1977,1980,1983,1985,1988],{"class":164,"line":738},[457,1967,1968],{"class":473},"    timeout ",[457,1970,634],{"class":469},[457,1972,1973],{"class":473}," httpx.Timeout(",[457,1975,1976],{"class":540},"float",[457,1978,1979],{"class":473},"(os.getenv(",[457,1981,1982],{"class":550},"\"DOCS_FETCH_TIMEOUT\"",[457,1984,554],{"class":473},[457,1986,1987],{"class":550},"\"30\"",[457,1989,1990],{"class":473},")))\n",[457,1992,1993,1995,1998,2001,2004,2006,2009,2012,2014,2016,2018,2021],{"class":164,"line":757},[457,1994,869],{"class":469},[457,1996,1997],{"class":469}," with",[457,1999,2000],{"class":473}," httpx.AsyncClient(",[457,2002,2003],{"class":643},"timeout",[457,2005,634],{"class":469},[457,2007,2008],{"class":473},"timeout, ",[457,2010,2011],{"class":643},"follow_redirects",[457,2013,634],{"class":469},[457,2015,765],{"class":540},[457,2017,798],{"class":473},[457,2019,2020],{"class":469},"as",[457,2022,2023],{"class":473}," client:\n",[457,2025,2026,2029,2031,2033],{"class":164,"line":770},[457,2027,2028],{"class":473},"        results ",[457,2030,634],{"class":469},[457,2032,1853],{"class":469},[457,2034,2035],{"class":473}," asyncio.gather(\n",[457,2037,2038,2041,2044,2047,2050,2053,2056,2059],{"class":164,"line":782},[457,2039,2040],{"class":469},"            *",[457,2042,2043],{"class":473},"(_fetch(client, name, url) ",[457,2045,2046],{"class":469},"for",[457,2048,2049],{"class":473}," name, url ",[457,2051,2052],{"class":469},"in",[457,2054,2055],{"class":473}," manifest[",[457,2057,2058],{"class":550},"\"assets\"",[457,2060,2061],{"class":473},"].items())\n",[457,2063,2064],{"class":164,"line":809},[457,2065,1011],{"class":473},[457,2067,2068,2071,2074,2076,2079],{"class":164,"line":815},[457,2069,2070],{"class":469},"    for",[457,2072,2073],{"class":473}," name, size ",[457,2075,2052],{"class":469},[457,2077,2078],{"class":540}," sorted",[457,2080,2081],{"class":473},"(results):\n",[457,2083,2084,2087,2089,2091,2093,2095,2098,2100,2102,2104,2107,2110,2113,2116,2118,2121],{"class":164,"line":820},[457,2085,2086],{"class":540},"        print",[457,2088,849],{"class":473},[457,2090,924],{"class":469},[457,2092,927],{"class":550},[457,2094,1142],{"class":540},[457,2096,2097],{"class":473},"name",[457,2099,1151],{"class":540},[457,2101,727],{"class":550},[457,2103,1142],{"class":540},[457,2105,2106],{"class":473},"size ",[457,2108,2109],{"class":469},"\u002F",[457,2111,2112],{"class":540}," 1024",[457,2114,2115],{"class":469},":.1f",[457,2117,1151],{"class":540},[457,2119,2120],{"class":550}," KiB\"",[457,2122,560],{"class":473},[457,2124,2125],{"class":164,"line":825},[457,2126,481],{"emptyLinePlaceholder":480},[457,2128,2129],{"class":164,"line":843},[457,2130,481],{"emptyLinePlaceholder":480},[457,2132,2133,2136,2139,2142,2145],{"class":164,"line":866},[457,2134,2135],{"class":469},"if",[457,2137,2138],{"class":540}," __name__",[457,2140,2141],{"class":469}," ==",[457,2143,2144],{"class":550}," \"__main__\"",[457,2146,840],{"class":473},[457,2148,2149],{"class":164,"line":888},[457,2150,2151],{"class":473},"    asyncio.run(main())\n",[14,2153,2154,2155,2158,2159,2162,2163,2167],{},"The manifest is four lines of TOML holding one pinned URL per file, so upgrading a renderer is a reviewable diff rather than a silent change to what your customers execute. Mount the directory with ",[24,2156,2157],{},"StaticFiles"," and set an immutable, one-year ",[24,2160,2161],{},"Cache-Control"," on the versioned path. Serving those bytes from your Python workers instead of a CDN or object storage is the one place this decision touches your hosting bill, and the fix costs nothing — see ",[18,2164,2166],{"href":2165},"\u002Fbuilding-monetizing-api-driven-micro-saas\u002Fdeploying-apis-to-render-or-vercel\u002F","deploying APIs to Render or Vercel"," for the static routing patterns.",[45,2169,2171],{"id":2170},"when-to-choose-each","When to choose each",[14,2173,2174],{},"Choose Swagger UI when the reader's next action is a request, not a decision. Pre-revenue APIs, internal tools, developer-facing utilities with under thirty operations, and anything sold on \"try it right now\" all convert better with an executable page. Choose ReDoc when the reader is implementing against you for a week: broad public APIs, anything with deeply nested schemas, anything an enterprise procurement team will read before signing, and anything you want indexed cleanly by search.",[14,2176,2177],{},"Avoid Swagger UI as your only page once your operations count passes roughly fifty, once any endpoint mutates billed state, or once you serve customers in regulated environments where an in-browser key paste is a finding. Avoid ReDoc as your only page while you are still selling the concept, because removing the try-it-out button from a self-serve funnel measurably lengthens the path from landing to first successful call. If a live playground would fire real charges, keep it pointed at a sandbox base URL and make the environment switch obvious.",[76,2179,85,2184,85,2187,85,2190,85,2192,85,2199,85,2206,85,2209,85,2213,85,2216,85,2220,85,2222,85,2224,85,2228,85,2232,85,2236,85,2239,85,2242,85,2245,85,2248,85,2251,85,2254,85,2258,85,2260,85,2262,85,2265,85,2268,85,2271,85,2273,85,2276,85,2279,85,2281,85,2284,85,2287,85,2290],{"viewBox":2180,"role":79,"ariaLabelledBy":2181,"xmlns":83,"style":84},"0 0 760 360",[2182,2183],"rvs-tree-t","rvs-tree-d",[87,2185,2186],{"id":2182},"Decision tree for choosing a docs renderer",[91,2188,2189],{"id":2183},"Branching from the docs audience: quick evaluators get Swagger UI, mixed public APIs get both routes, and enterprise integrators get ReDoc, with a reminder to gate the playground.",[95,2191],{"x":97,"y":97,"width":98,"height":342,"fill":100},[102,2193,104,2194,85],{},[106,2195,114,2197,104],{"id":2196,"viewBox":109,"refX":110,"refY":111,"markerWidth":112,"markerHeight":112,"orient":113},"rvs-tree-arrow",[116,2198],{"d":118,"fill":119},[95,2200],{"x":2201,"y":2202,"width":2203,"height":2204,"rx":2205,"fill":100,"stroke":187,"style":152},"290","20","180","44","10",[121,2207,2208],{"x":123,"y":146,"fill":125,"style":142},"Who reads the docs?",[164,2210],{"x1":123,"y1":2211,"x2":123,"y2":2212,"stroke":119,"style":152},"64","86",[164,2214],{"x1":401,"y1":2212,"x2":2215,"y2":2212,"stroke":119,"style":152},"640",[164,2217],{"x1":401,"y1":2212,"x2":401,"y2":2218,"stroke":119,"style":2219},"108","stroke-width:2;marker-end:url(#rvs-tree-arrow);",[164,2221],{"x1":123,"y1":2212,"x2":123,"y2":2218,"stroke":119,"style":2219},[164,2223],{"x1":2215,"y1":2212,"x2":2215,"y2":2218,"stroke":119,"style":2219},[95,2225],{"x":2202,"y":2226,"width":26,"height":2227,"rx":2205,"fill":135,"stroke":136,"style":137},"110","56",[121,2229,2231],{"x":401,"y":2230,"fill":125,"style":156},"134","Trial user, quick eval",[121,2233,2235],{"x":401,"y":2234,"fill":119,"style":161},"152","wants a live 200 today",[95,2237],{"x":2238,"y":2226,"width":26,"height":2227,"rx":2205,"fill":135,"stroke":136,"style":137},"280",[121,2240,2241],{"x":123,"y":2230,"fill":125,"style":156},"Public API, mixed use",[121,2243,2244],{"x":123,"y":2234,"fill":119,"style":161},"eval plus integration",[95,2246],{"x":2247,"y":2226,"width":26,"height":2227,"rx":2205,"fill":135,"stroke":136,"style":137},"540",[121,2249,2250],{"x":2215,"y":2230,"fill":125,"style":156},"Enterprise integrator",[121,2252,2253],{"x":2215,"y":2234,"fill":119,"style":161},"reads every field once",[164,2255],{"x1":401,"y1":2256,"x2":401,"y2":2257,"stroke":119,"style":2219},"166","208",[164,2259],{"x1":123,"y1":2256,"x2":123,"y2":2257,"stroke":119,"style":2219},[164,2261],{"x1":2215,"y1":2256,"x2":2215,"y2":2257,"stroke":119,"style":2219},[95,2263],{"x":2202,"y":2264,"width":26,"height":2227,"rx":2205,"fill":100,"stroke":151,"style":152},"210",[121,2266,2267],{"x":401,"y":422,"fill":125,"style":156},"Swagger UI at \u002Fdocs",[121,2269,2270],{"x":401,"y":352,"fill":119,"style":161},"try-it-out enabled",[95,2272],{"x":2238,"y":2264,"width":26,"height":2227,"rx":2205,"fill":100,"stroke":226,"style":152},[121,2274,2275],{"x":123,"y":422,"fill":125,"style":156},"Ship both routes",[121,2277,2278],{"x":123,"y":352,"fill":119,"style":161},"\u002Fdocs and \u002Freference",[95,2280],{"x":2247,"y":2264,"width":26,"height":2227,"rx":2205,"fill":100,"stroke":206,"style":152},[121,2282,2283],{"x":2215,"y":422,"fill":125,"style":156},"ReDoc at \u002Freference",[121,2285,2286],{"x":2215,"y":352,"fill":119,"style":161},"no live calls fired",[95,2288],{"x":2202,"y":2289,"width":338,"height":2204,"rx":2205,"fill":135,"stroke":136,"style":137},"296",[121,2291,2293],{"x":123,"y":2292,"fill":125,"style":156},"323","Gate \u002Fdocs behind auth when try-it-out can mutate billed state",[45,2295,2297],{"id":2296},"migrating-from-one-to-the-other","Migrating from one to the other",[14,2299,2300,2301,2305],{},"Switching renderers is not a rewrite, because the spec is the interface and neither tool touches it. The only real work is redirects and analytics. Add the new route alongside the old one, keep both live for a release, then decide with data rather than taste — and if the old path was public and indexed, treat retiring it the same way you would treat ",[18,2302,2304],{"href":2303},"\u002Fscaling-and-operating-production-python-apis\u002Fversioning-and-evolving-public-apis\u002Fdeprecating-an-api-endpoint-without-breaking-customers\u002F","deprecating an API endpoint without breaking customers",": announce it, redirect it, never delete it silently.",[76,2307,85,2312,85,2315,85,2318,85,2320,85,2323,85,2328,85,2332,85,2335,85,2338,85,2341,85,2344,85,2347,85,2349,85,2352,85,2356,85,2360,85,2363,85,2366,85,2368,85,2371,85,2374,85,2377,85,2380,85,2383,85,2386,85,2389,85,2392,85,2395,85,2398],{"viewBox":2308,"role":79,"ariaLabelledBy":2309,"xmlns":83,"style":84},"0 0 760 200",[2310,2311],"rvs-mig-t","rvs-mig-d",[87,2313,2314],{"id":2310},"Four-step path for switching docs renderers",[91,2316,2317],{"id":2311},"Timeline of four steps: add the second route, vendor the pinned assets, measure the traffic split for two weeks, then gate or redirect the losing route.",[95,2319],{"x":97,"y":97,"width":98,"height":26,"fill":100},[121,2321,2322],{"x":123,"y":124,"fill":125,"style":126},"Two-week switchover, no spec changes",[164,2324],{"x1":2325,"y1":2326,"x2":2327,"y2":2326,"stroke":136,"style":152},"101","70","659",[2329,2330],"circle",{"cx":2325,"cy":2326,"r":2331,"fill":100,"stroke":187,"style":152},"13",[121,2333,733],{"x":2325,"y":2334,"fill":125,"style":156},"75",[2329,2336],{"cx":2337,"cy":2326,"r":2331,"fill":100,"stroke":206,"style":152},"287",[121,2339,2340],{"x":2337,"y":2334,"fill":125,"style":156},"2",[2329,2342],{"cx":2343,"cy":2326,"r":2331,"fill":100,"stroke":226,"style":152},"473",[121,2345,2346],{"x":2343,"y":2334,"fill":125,"style":156},"3",[2329,2348],{"cx":2327,"cy":2326,"r":2331,"fill":100,"stroke":151,"style":152},[121,2350,2351],{"x":2327,"y":2334,"fill":125,"style":156},"4",[95,2353],{"x":130,"y":147,"width":2354,"height":2355,"rx":2205,"fill":135,"stroke":136,"style":137},"170","80",[121,2357,2359],{"x":2325,"y":2358,"fill":125,"style":156},"118","Add the route",[121,2361,2362],{"x":2325,"y":397,"fill":119,"style":161},"new path, same spec",[121,2364,2365],{"x":2325,"y":171,"fill":119,"style":161},"keep the old one",[95,2367],{"x":182,"y":147,"width":2354,"height":2355,"rx":2205,"fill":135,"stroke":136,"style":137},[121,2369,2370],{"x":2337,"y":2358,"fill":125,"style":156},"Vendor assets",[121,2372,2373],{"x":2337,"y":397,"fill":119,"style":161},"pin every version",[121,2375,2376],{"x":2337,"y":171,"fill":119,"style":161},"drop the public CDN",[95,2378],{"x":2379,"y":147,"width":2354,"height":2355,"rx":2205,"fill":135,"stroke":136,"style":137},"388",[121,2381,2382],{"x":2343,"y":2358,"fill":125,"style":156},"Measure the split",[121,2384,2385],{"x":2343,"y":397,"fill":119,"style":161},"log both page views",[121,2387,2388],{"x":2343,"y":171,"fill":119,"style":161},"two weeks of data",[95,2390],{"x":2391,"y":147,"width":2354,"height":2355,"rx":2205,"fill":135,"stroke":136,"style":137},"574",[121,2393,2394],{"x":2327,"y":2358,"fill":125,"style":156},"Gate or redirect",[121,2396,2397],{"x":2327,"y":397,"fill":119,"style":161},"301 the loser",[121,2399,2400],{"x":2327,"y":171,"fill":119,"style":161},"link both in the portal",[14,2402,2403,2404,2408],{},"Step three is the one builders skip. Emit a lightweight event on each docs page render and store it next to your other product events, the way ",[18,2405,2407],{"href":2406},"\u002Fbuilding-monetizing-api-driven-micro-saas\u002Ftracking-api-usage-and-analytics\u002F","tracking API usage and analytics"," describes, then compare which page precedes a first successful authenticated call. That number, not a preference, tells you which renderer earns its place on your primary docs URL. If the split is genuinely even, keep both — the marginal cost is one static file and one route.",[14,2410,2411,2412,2415,2416,2419,2420,2424],{},"One caveat worth planning for: if your spec relies on OpenAPI 3.1 features such as ",[24,2413,2414],{},"null"," in a type union, webhooks, or complex ",[24,2417,2418],{},"oneOf"," discriminators, render it in both tools before you commit. Support has historically lagged in one renderer or the other, and the failure mode is a silently blank schema block rather than an error. The metadata work in ",[18,2421,2423],{"href":2422},"\u002Fgetting-started-with-python-apis-for-builders\u002Fdocumenting-apis-with-openapi\u002Fcustomizing-fastapi-openapi-schema\u002F","customizing the FastAPI OpenAPI schema"," is what makes either page look professional, so do that first and pick the renderer second.",[45,2426,2428],{"id":2427},"builder-verdict","Builder verdict",[14,2430,2431,2432,2434,2435,2437],{},"Make ReDoc your canonical reference and keep Swagger UI as a gated playground. ReDoc gives you a page that stays fast and readable as your surface grows, themes cleanly from environment variables so a white-label deployment costs nothing, and never turns a docs visit into billed traffic or a key pasted into a browser tab. It is the page you want ranking, linked from your pricing page, and read by the engineer who has to justify your invoice. Swagger UI still earns its keep in the funnel: nothing converts a skeptical trial user like a green ",[24,2433,26],{}," they triggered themselves, so serve it at ",[24,2436,38],{},", point it at a sandbox base URL, and put it behind whatever login your free tier already has. Two routes over one spec is maybe forty lines of Python and one vendored asset directory — far cheaper than picking wrong and rewriting your docs story after your first enterprise customer asks for a PDF-able reference.",[45,2439,2441],{"id":2440},"faq","FAQ",[14,2443,2444,2447],{},[53,2445,2446],{},"Does serving a live try-it-out console cost me real money?","\nYes, and it is easy to underestimate. Every Try it out click is a billed request against your own infrastructure, plus any third-party API you proxy. A docs page that gets a thousand visits a month with a ten percent click rate adds a hundred real calls — trivial for a cached read endpoint, painful if each one triggers an LLM call or a paid data lookup. Point the playground at a sandbox server with capped quotas and it costs cents.",[14,2449,2450,2453],{},[53,2451,2452],{},"Which renderer is better for organic search traffic to my docs?","\nReDoc, by a clear margin. Its deep-linkable anchors, static-looking markup, and description-first layout give crawlers real prose per operation, and it stays fast on mobile at large spec sizes. Swagger UI's collapsed accordion hides most of your content behind interaction and renders thin pages that rank poorly.",[14,2455,2456,2459,2460,1697],{},[53,2457,2458],{},"How risky is loading either bundle from a public CDN?","\nRisky enough to fix in an afternoon. A compromised or unavailable CDN takes your documentation offline or executes untrusted JavaScript on a page where customers paste live credentials. Vendor pinned copies, serve them from your own static path, and treat a renderer upgrade as a reviewed dependency bump. If keys have ever been pasted into a page you no longer trust, follow your process for ",[18,2461,2463],{"href":2462},"\u002Fgetting-started-with-python-apis-for-builders\u002Fhandling-api-authentication-in-python\u002Frotating-api-keys-without-downtime\u002F","rotating API keys without downtime",[14,2465,2466,2469],{},[53,2467,2468],{},"What does it cost to switch renderers later?","\nAlmost nothing in code and a little in traffic. The spec does not change, so the work is one new route, a vendored asset, and a redirect. The real cost is any inbound links and search rankings pointing at the retired path, which is why you should redirect rather than delete and keep the old URL alive for at least a full release cycle.",[14,2471,2472,2475],{},[53,2473,2474],{},"Can I keep the playground private without building a login?","\nYes. Gate the route with a dependency that checks a signed, expiring token in the query string, generate those links from your dashboard or onboarding email, and leave the ReDoc reference fully public. That keeps your marketing surface open, keeps the executable surface tied to a known account, and takes about twenty lines with the auth patterns you already have.",[45,2477,2479],{"id":2478},"related","Related",[2481,2482,2483,2489,2495,2501,2508],"ul",{},[2484,2485,2486,2488],"li",{},[18,2487,21],{"href":20}," — the parent guide covering spec metadata, versioned export, and client generation.",[2484,2490,2491,2494],{},[18,2492,2493],{"href":2422},"Customizing the FastAPI OpenAPI Schema"," — fix operation IDs, examples, and tags before you argue about renderers.",[2484,2496,2497,2500],{},[18,2498,2499],{"href":1695},"Creating a Developer Portal for Your API"," — where these two docs routes sit inside the wider self-serve surface.",[2484,2502,2503,2507],{},[18,2504,2506],{"href":2505},"\u002Fscaling-and-operating-production-python-apis\u002Fversioning-and-evolving-public-apis\u002F","Versioning and Evolving Public APIs"," — publishing a stable spec per version so both renderers stay accurate.",[2484,2509,2510,2513],{},[18,2511,2512],{"href":33},"Setting Up FastAPI"," — the app configuration these docs routes attach to.",[2515,2516,2517],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":453,"searchDepth":466,"depth":466,"links":2519},[2520,2521,2522,2523,2524,2525,2526,2527,2528],{"id":47,"depth":466,"text":48},{"id":235,"depth":466,"text":236},{"id":437,"depth":466,"text":438},{"id":1700,"depth":466,"text":1701},{"id":2170,"depth":466,"text":2171},{"id":2296,"depth":466,"text":2297},{"id":2427,"depth":466,"text":2428},{"id":2440,"depth":466,"text":2441},{"id":2478,"depth":466,"text":2479},"Compare ReDoc and Swagger UI for a paid Python API: live try-it-out versus reference readability, bundle weight, theming, self-hosting, and when to serve both.","md",{"pageTitle":2532,"type":2533,"datePublished":2534,"dateModified":2534},"ReDoc vs Swagger UI: Picking the API Docs Renderer to Ship","article","2026-07-23","\u002Fgetting-started-with-python-apis-for-builders\u002Fdocumenting-apis-with-openapi\u002Fredoc-vs-swagger-ui",{"title":5,"description":2529},"getting-started-with-python-apis-for-builders\u002Fdocumenting-apis-with-openapi\u002Fredoc-vs-swagger-ui\u002Findex","CMu72vGdf9O7TatMUkkPhHRQvV6Q-5VlxD1wFyBwIxQ",{"@context":2540,"@type":2541,"mainEntity":2542},"https:\u002F\u002Fschema.org","FAQPage",[2543,2548,2551,2554,2557],{"@type":2544,"name":2446,"acceptedAnswer":2545},"Question",{"@type":2546,"text":2547},"Answer","Yes, and it is easy to underestimate. Every Try it out click is a billed request against your own infrastructure, plus any third-party API you proxy. A docs page that gets a thousand visits a month with a ten percent click rate adds a hundred real calls — trivial for a cached read endpoint, painful if each one triggers an LLM call or a paid data lookup. Point the playground at a sandbox server with capped quotas and it costs cents.",{"@type":2544,"name":2452,"acceptedAnswer":2549},{"@type":2546,"text":2550},"ReDoc, by a clear margin. Its deep-linkable anchors, static-looking markup, and description-first layout give crawlers real prose per operation, and it stays fast on mobile at large spec sizes. Swagger UI's collapsed accordion hides most of your content behind interaction and renders thin pages that rank poorly.",{"@type":2544,"name":2458,"acceptedAnswer":2552},{"@type":2546,"text":2553},"Risky enough to fix in an afternoon. A compromised or unavailable CDN takes your documentation offline or executes untrusted JavaScript on a page where customers paste live credentials. Vendor pinned copies, serve them from your own static path, and treat a renderer upgrade as a reviewed dependency bump. If keys have ever been pasted into a page you no longer trust, follow your process for rotating API keys without downtime.",{"@type":2544,"name":2468,"acceptedAnswer":2555},{"@type":2546,"text":2556},"Almost nothing in code and a little in traffic. The spec does not change, so the work is one new route, a vendored asset, and a redirect. The real cost is any inbound links and search rankings pointing at the retired path, which is why you should redirect rather than delete and keep the old URL alive for at least a full release cycle.",{"@type":2544,"name":2474,"acceptedAnswer":2558},{"@type":2546,"text":2559},"Yes. Gate the route with a dependency that checks a signed, expiring token in the query string, generate those links from your dashboard or onboarding email, and leave the ReDoc reference fully public. That keeps your marketing surface open, keeps the executable surface tied to a known account, and takes about twenty lines with the auth patterns you already have.",1784887028458]