-
-
Notifications
You must be signed in to change notification settings - Fork 26
Expand file tree
/
Copy path3d.html
More file actions
379 lines (349 loc) · 24.5 KB
/
Copy path3d.html
File metadata and controls
379 lines (349 loc) · 24.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
<!doctype html>
<html lang="en">
<head>
<script>/* three.ws theme boot — no-flash; runtime in /theme-switcher.js */(function(){try{var m=localStorage.getItem('twx_theme');var l=m==='auto'?(window.matchMedia&&window.matchMedia('(prefers-color-scheme: light)').matches):m==='light';document.documentElement.setAttribute('data-theme',l?'light':'dark');}catch(e){document.documentElement.setAttribute('data-theme','dark');}})();</script>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title data-i18n="3d.meta_title">three.ws 3D API — free text→3D + inspection for AI agents</title>
<meta name="description" content="A free, keyless 3D API for AI agents: turn a text prompt into a real GLB, validate and optimize any glTF/GLB, and graduate to paid Forge Pro quality tiers and Rigged Avatars. One URL to discover it all — no account, no API key." data-i18n-attr="content:3d.meta_description"/>
<meta name="theme-color" content="#0b0b0b" />
<meta property="og:title" content="three.ws 3D API — free text→3D + inspection for AI agents" data-i18n-attr="content:3d.meta_og_title"/>
<meta property="og:description" content="Free, keyless 3D for agents: text→3D draft generation + glTF/GLB inspection, with a clean OpenAPI 3.1 doc and a paid ladder to production quality." data-i18n-attr="content:3d.meta_og_description"/>
<meta property="og:type" content="website" />
<meta property="og:url" content="https://three.ws/3d" />
<link rel="canonical" href="https://three.ws/3d" />
<link rel="stylesheet" href="/style.css" />
<link rel="stylesheet" href="/nav.css" />
<script
src="https://ajax.googleapis.com/ajax/libs/model-viewer/4.0.0/model-viewer.min.js"
integrity="sha384-sr9b4Ux0WhAUGclJ0ym0FSY2zSOMmNSn0bP/SA0e6bNCrpn/5W3QL8mm+LdlQMKw"
crossorigin="anonymous"
type="module"
></script>
<script src="/model-viewer-meshopt.js"></script>
<style>
.ws3d { max-width: 980px; margin: 0 auto; padding: 0 var(--space-md) var(--space-2xl); }
.ws3d a { color: var(--accent); text-decoration: none; }
.ws3d a:hover { text-decoration: underline; }
/* Hero */
.hero { padding: var(--space-2xl) 0 var(--space-lg); }
.hero .badges { display: flex; gap: var(--space-2xs); flex-wrap: wrap; margin-bottom: var(--space-sm); }
.pill {
font-family: var(--font-mono); font-size: var(--text-2xs); font-weight: var(--weight-semibold);
text-transform: uppercase; letter-spacing: .06em;
padding: .25rem .6rem; border-radius: var(--radius-pill);
background: var(--accent-soft); color: var(--ink-bright); border: 1px solid var(--stroke);
}
.pill.live { display: inline-flex; align-items: center; gap: .4rem; }
.pill.live .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--ink-faint); transition: background var(--duration-base) var(--ease-standard); }
.pill.live.up .dot { background: var(--success); box-shadow: 0 0 8px var(--success); }
.pill.live.down .dot { background: var(--warn); }
.hero h1 { font-family: var(--font-display); font-size: var(--text-3xl); line-height: var(--leading-tight); margin: 0 0 var(--space-sm); letter-spacing: -.02em; }
.hero p.lede { font-size: var(--text-lg); color: var(--ink-dim); max-width: 62ch; margin: 0 0 var(--space-lg); }
.hero .cta { display: flex; gap: var(--space-2xs); flex-wrap: wrap; }
.btn {
display: inline-flex; align-items: center; gap: .4rem;
font-weight: var(--weight-semibold); font-size: var(--text-ui);
padding: .6rem 1rem; border-radius: var(--radius-md);
border: 1px solid var(--stroke); background: var(--surface-1); color: var(--ink-bright);
cursor: pointer; transition: transform var(--duration-fast) var(--ease-standard), border-color var(--duration-fast) var(--ease-standard), background var(--duration-fast) var(--ease-standard);
}
.btn:hover { border-color: var(--stroke-strong); transform: translateY(-1px); text-decoration: none; }
.btn:active { transform: translateY(0); }
.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.btn.primary { background: var(--ink-bright); color: var(--bg-0); border-color: var(--ink-bright); }
/* Stat strip */
.stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-sm); margin: var(--space-lg) 0; }
.stat { background: var(--surface-1); border: 1px solid var(--stroke); border-radius: var(--radius-lg); padding: var(--space-md); }
.stat .n { font-family: var(--font-display); font-size: var(--text-2xl); color: var(--ink-bright); }
.stat .l { font-size: var(--text-xs); color: var(--ink-dim); text-transform: uppercase; letter-spacing: .05em; }
section.block { margin-top: var(--space-2xl); }
section.block > h2 { font-family: var(--font-display); font-size: var(--text-xl); margin: 0 0 var(--space-xs); letter-spacing: -.01em; }
section.block > p.sub { color: var(--ink-dim); margin: 0 0 var(--space-md); max-width: 64ch; }
/* Endpoint table */
.table-wrap { overflow-x: auto; border: 1px solid var(--stroke); border-radius: var(--radius-lg); }
table.ep { width: 100%; border-collapse: collapse; font-size: var(--text-sm); min-width: 640px; }
table.ep th, table.ep td { text-align: left; padding: .7rem .9rem; border-bottom: 1px solid var(--stroke); vertical-align: top; }
table.ep th { font-size: var(--text-2xs); text-transform: uppercase; letter-spacing: .06em; color: var(--ink-dim); background: var(--surface-1); }
table.ep tr:last-child td { border-bottom: none; }
table.ep code { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--ink-bright); }
.verb { font-family: var(--font-mono); font-size: var(--text-2xs); font-weight: var(--weight-bold); padding: .1rem .4rem; border-radius: var(--radius-sm); background: var(--accent-soft); color: var(--ink-bright); }
.tier-tag { font-size: var(--text-2xs); color: var(--success); font-weight: var(--weight-semibold); }
/* Code + run */
pre.code { background: var(--bg-1); border: 1px solid var(--stroke); border-radius: var(--radius-md); padding: var(--space-sm) var(--space-md); overflow-x: auto; font-family: var(--font-mono); font-size: var(--text-xs); line-height: 1.6; margin: 0; }
pre.code code { color: var(--ink); }
.runbar { display: flex; align-items: center; gap: var(--space-2xs); margin: var(--space-sm) 0 var(--space-2xs); flex-wrap: wrap; }
.runbar .status { font-size: var(--text-xs); color: var(--ink-dim); }
.out { margin-top: var(--space-2xs); }
.out pre.code { max-height: 340px; overflow: auto; }
/* Viewer */
.viewer-card { display: grid; grid-template-columns: 1.2fr 1fr; gap: var(--space-lg); align-items: center; background: var(--surface-1); border: 1px solid var(--stroke); border-radius: var(--radius-lg); padding: var(--space-lg); }
model-viewer { width: 100%; height: 320px; background: radial-gradient(circle at 50% 30%, var(--surface-2), var(--bg-1)); border-radius: var(--radius-md); --poster-color: transparent; }
.viewer-card h3 { margin: 0 0 var(--space-2xs); font-family: var(--font-display); }
.viewer-card p { color: var(--ink-dim); margin: 0 0 var(--space-sm); font-size: var(--text-sm); }
/* Ladder */
.ladder { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-sm); }
.rung { background: var(--surface-1); border: 1px solid var(--stroke); border-radius: var(--radius-lg); padding: var(--space-md); display: flex; flex-direction: column; }
.rung.free { border-color: var(--success); }
.rung .cap { font-size: var(--text-2xs); text-transform: uppercase; letter-spacing: .06em; color: var(--ink-dim); }
.rung h3 { font-family: var(--font-display); margin: .3rem 0; font-size: var(--text-lg); }
.rung .price { font-family: var(--font-mono); font-size: var(--text-sm); color: var(--ink-bright); margin-bottom: var(--space-2xs); }
.rung.free .price { color: var(--success); }
.rung ul { margin: var(--space-2xs) 0 var(--space-sm); padding-left: 1.1rem; color: var(--ink-dim); font-size: var(--text-sm); }
.rung ul li { margin: .2rem 0; }
.rung .go { margin-top: auto; }
.faq { display: grid; gap: var(--space-sm); }
.faq details { background: var(--surface-1); border: 1px solid var(--stroke); border-radius: var(--radius-md); padding: var(--space-sm) var(--space-md); }
.faq summary { cursor: pointer; font-weight: var(--weight-semibold); color: var(--ink-bright); }
.faq p { color: var(--ink-dim); margin: var(--space-2xs) 0 0; font-size: var(--text-sm); }
footer.foot { margin-top: var(--space-2xl); padding-top: var(--space-lg); border-top: 1px solid var(--stroke); color: var(--ink-dim); font-size: var(--text-sm); }
@media (max-width: 760px) {
.stats, .ladder { grid-template-columns: 1fr; }
.viewer-card { grid-template-columns: 1fr; }
.hero h1 { font-size: var(--text-2xl); }
}
</style>
<meta name="robots" content="index, follow, max-image-preview:large, max-snippet:-1">
<meta property="og:site_name" content="three.ws">
<meta property="og:locale" content="en_US">
<meta property="og:image" content="https://three.ws/api/page-og?s=learn&t=3D+API&d=Free%2C+keyless+3D+API+for+AI+agents%3A+turn+a+text+prompt+into+a+real+GLB+model+and+validate%2Foptimize+any+glTF%2FGLB%2C+all+from+one+URL+with+a+live+OpenAPI+3.1+spec.&p=%2F3d">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:type" content="image/png">
<meta property="og:image:alt" content="3D API — three.ws">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@trythreews">
<meta name="twitter:title" content="3D API · three.ws" data-i18n-attr="content:3d.meta_twitter_title">
<meta name="twitter:description" content="Free, keyless 3D API for AI agents: turn a text prompt into a real GLB model and validate/optimize any glTF/GLB, all from one URL with a live OpenAPI 3.1 spec. Free draft generation and inspection graduate to paid Forge Pro quality tiers and animation-ready Rigged Avatars. No key, no account." data-i18n-attr="content:3d.meta_twitter_description">
<meta name="twitter:image" content="https://three.ws/api/page-og?s=learn&t=3D+API&d=Free%2C+keyless+3D+API+for+AI+agents%3A+turn+a+text+prompt+into+a+real+GLB+model+and+validate%2Foptimize+any+glTF%2FGLB%2C+all+from+one+URL+with+a+live+OpenAPI+3.1+spec.&p=%2F3d">
<script type="application/ld+json">{"@context":"https://schema.org","@graph":[{"@type":"WebPage","name":"3D API","description":"Free, keyless 3D API for AI agents: turn a text prompt into a real GLB model and validate/optimize any glTF/GLB, all from one URL with a live OpenAPI 3.1 spec. Free draft generation and inspection graduate to paid Forge Pro quality tiers and animation-ready Rigged Avatars. No key, no account.","url":"https://three.ws/3d","isPartOf":{"@type":"WebSite","name":"three.ws","url":"https://three.ws"},"primaryImageOfPage":"https://three.ws/api/page-og?s=learn&t=3D+API&d=Free%2C+keyless+3D+API+for+AI+agents%3A+turn+a+text+prompt+into+a+real+GLB+model+and+validate%2Foptimize+any+glTF%2FGLB%2C+all+from+one+URL+with+a+live+OpenAPI+3.1+spec.+&p=%2F3d"}]}</script>
</head>
<body>
<script src="/nav.js"></script>
<main id="main-content" class="ws3d">
<!-- Hero -->
<header class="hero">
<div class="badges">
<span class="pill" data-i18n="3d.free">Free</span>
<span class="pill" data-i18n="3d.keyless">Keyless</span>
<span class="pill" data-i18n="3d.openapi_3_1">OpenAPI 3.1</span>
<span class="pill live" id="liveBadge" title="Live status of the API" data-i18n-html="3d.checking" data-i18n-attr="title:3d.live_status_of_the_api_title"><span class="dot"></span><span id="liveText">checking…</span></span>
</div>
<h1 data-i18n="3d.three_ws_3d_api">three.ws 3D API</h1>
<p class="lede" data-i18n-html="3d.give_your_ai_agent_a_3d_toolchain">Give your AI agent a 3D toolchain. Turn a text prompt into a real textured GLB, validate and optimize any glTF/GLB — <strong>free, keyless, no account</strong>. When a draft isn't enough, the same API graduates to production Forge Pro tiers and animation-ready Rigged Avatars.</p>
<div class="cta">
<a class="btn primary" href="#quickstart" data-i18n="3d.quickstart">Quickstart</a>
<a class="btn" href="/api/3d/openapi.json" data-i18n="3d.openapi_spec">OpenAPI spec →</a>
<a class="btn" href="/api/3d" data-i18n="3d.json_index">JSON index →</a>
</div>
</header>
<!-- Live stats -->
<div class="stats" aria-label="API at a glance">
<div class="stat"><div class="n" id="statEndpoints">—</div><div class="l">Free endpoints</div></div>
<div class="stat"><div class="n">$0</div><div class="l">Cost to start</div></div>
<div class="stat"><div class="n">0</div><div class="l">API keys required</div></div>
</div>
<!-- Who it's for -->
<section class="block">
<h2 data-i18n="3d.who_this_is_for">Who this is for</h2>
<p class="sub" data-i18n-html="3d.an_autonomous_agent_building_a_game_a">An autonomous agent building a game, a scene, an NFT, or a marketplace asset needs 3D — and needs to trust what it gets. This API answers both mid-task: <strong>make me a model from this description</strong>, and <strong>is this model any good, and how do I make it smaller?</strong> No other agent-payments platform gives away 3D generation and inspection.</p>
</section>
<!-- Endpoints -->
<section class="block" id="endpoints">
<h2 data-i18n="3d.endpoints">Endpoints</h2>
<p class="sub" data-i18n="3d.assembled_live_from_the_api_catalog_every">Assembled live from the API catalog. Every row is free and keyless.</p>
<div class="table-wrap">
<table class="ep">
<thead>
<tr><th data-i18n="3d.method">Method</th><th data-i18n="3d.path">Path</th><th data-i18n="3d.what_it_does">What it does</th><th data-i18n="3d.tier">Tier</th></tr>
</thead>
<tbody id="epRows">
<tr><td colspan="4" style="color:var(--ink-dim)" data-i18n="3d.loading_catalog">Loading catalog…</td></tr>
</tbody>
</table>
</div>
</section>
<!-- Quickstart -->
<section class="block" id="quickstart">
<h2 data-i18n="3d.quickstart_2">Quickstart</h2>
<p class="sub" data-i18n="3d.copy_run_done_no_key_no_header">Copy, run, done. No key, no header, no account. These call the live production API.</p>
<h3 style="font-family:var(--font-display);font-size:var(--text-md);margin:var(--space-md) 0 var(--space-2xs)" data-i18n="3d.1_discover_the_whole_api">1 · Discover the whole API</h3>
<pre class="code"><code>curl https://three.ws/api/3d</code></pre>
<h3 style="font-family:var(--font-display);font-size:var(--text-md);margin:var(--space-md) 0 var(--space-2xs)" data-i18n="3d.2_inspect_a_model_validate_optimization_repo">2 · Inspect a model (validate + optimization report)</h3>
<pre class="code"><code>curl "https://three.ws/api/3d/inspect?url=https://three.ws/avatars/cesium-man.glb"</code></pre>
<div class="runbar">
<button class="btn" id="runInspect" type="button" data-i18n="3d.run_it_live">Run it live ▶</button>
<span class="status" id="inspectStatus"></span>
</div>
<div class="out"><pre class="code"><code id="inspectOut">// Response appears here — a real call to /api/3d/inspect.</code></pre></div>
<h3 style="font-family:var(--font-display);font-size:var(--text-md);margin:var(--space-md) 0 var(--space-2xs)" data-i18n="3d.3_generate_a_model_from_text_free">3 · Generate a model from text (free draft)</h3>
<pre class="code"><code>curl -X POST https://three.ws/api/3d/generate \
-H 'content-type: application/json' \
-d '{ "prompt": "a small ceramic robot figurine" }'
# → { "status": "pending", "job": "…", "poll": "/api/3d/generate?job=…" }
# poll the job:
curl "https://three.ws/api/3d/generate?job=<id>"
# → { "status": "done", "glbUrl": "https://…/model.glb", "viewerUrl": "https://three.ws/…" }</code></pre>
<h3 style="font-family:var(--font-display);font-size:var(--text-md);margin:var(--space-md) 0 var(--space-2xs)" data-i18n="3d.point_any_openapi_toolchain_at_it">Point any OpenAPI toolchain at it</h3>
<pre class="code"><code># LangChain, openapi-generator, Swagger UI, etc.
curl https://three.ws/api/3d/openapi.json</code></pre>
</section>
<!-- Viewer -->
<section class="block">
<h2 data-i18n="3d.see_the_output">See the output</h2>
<p class="sub" data-i18n="3d.everything_the_api_touches_is_a_real">Everything the API touches is a real, standards-compliant GLB — the same format your engine, viewer, or marketplace already speaks. Drag to orbit.</p>
<div class="viewer-card">
<model-viewer
src="/avatars/cesium-man.glb"
alt="A rigged 3D character model rendered from a GLB file"
camera-controls
auto-rotate
tone-mapping="aces"
environment-image="/environments/gallery/env.hdr"
shadow-intensity="1"
shadow-softness="0.9"
exposure="1.5"
ar
touch-action="pan-y"
></model-viewer>
<div>
<h3 data-i18n="3d.real_glb_not_a_screenshot">Real GLB, not a screenshot</h3>
<p data-i18n-html="3d.this_is_a_live_gltf_binary_rendered">This is a live glTF binary rendered in your browser. The free <code>/api/3d/inspect</code> endpoint reports exactly what's inside one of these — vertices, triangles, materials, textures, animations, extensions — and a prioritized plan to make it smaller and faster.</p>
<a class="btn" href="/api/3d/inspect?url=https://three.ws/avatars/cesium-man.glb" data-i18n="3d.inspect_this_model">Inspect this model →</a>
</div>
</div>
</section>
<!-- Free → paid ladder -->
<section class="block">
<h2 data-i18n="3d.free_paid_ladder">Free → paid ladder</h2>
<p class="sub" data-i18n-html="3d.start_free_upgrade_only_when_a_draft">Start free. Upgrade only when a draft isn't enough. Paid tiers settle per-call in USDC over <a href="/pay">x402</a> — still no account, still no key.</p>
<div class="ladder">
<div class="rung free">
<span class="cap" data-i18n="3d.tier_0_free">Tier 0 · Free</span>
<h3 data-i18n="3d.draft_inspect">Draft + Inspect</h3>
<div class="price">$0 · keyless</div>
<ul>
<li data-i18n="3d.text_3d_draft_nim_trellis_single_subject">Text→3D draft (NIM TRELLIS): single subject, GLB, no rigging</li>
<li data-i18n="3d.gltf_glb_validation_optimization_report">glTF/GLB validation + optimization report</li>
<li data-i18n="3d.generous_per_ip_rate_limit">Generous per-IP rate limit</li>
</ul>
<div class="go"><a class="btn" href="/api/3d" data-i18n="3d.use_it">Use it →</a></div>
</div>
<div class="rung">
<span class="cap" data-i18n="3d.tier_1_paid">Tier 1 · Paid</span>
<h3 data-i18n="3d.forge_pro">Forge Pro</h3>
<div class="price">from $0.05 USDC</div>
<ul>
<li data-i18n="3d.higher_polygon_budgets_pbr_textures">Higher polygon budgets + PBR textures</li>
<li data-i18n="3d.draft_standard_high_quality_tiers">draft / standard / high quality tiers</li>
<li data-i18n="3d.text_3d_and_image_3d_pipelines">Text→3D and image→3D pipelines</li>
</ul>
<div class="go"><a class="btn" href="/forge" data-i18n="3d.open_forge">Open Forge →</a></div>
</div>
<div class="rung">
<span class="cap" data-i18n="3d.tier_2_paid">Tier 2 · Paid</span>
<h3 data-i18n="3d.rigged_avatars">Rigged Avatars</h3>
<div class="price">from $0.05 USDC</div>
<ul>
<li data-i18n="3d.auto_skeleton_skin_weights">Auto skeleton + skin weights</li>
<li data-i18n="3d.pre_baked_idle_walk_clip_library">Pre-baked idle / walk clip library</li>
<li data-i18n="3d.animation_ready_humanoid_glb">Animation-ready humanoid GLB</li>
</ul>
<div class="go"><a class="btn" href="/create/prompt" data-i18n="3d.rig_one">Rig one →</a></div>
</div>
</div>
</section>
<!-- FAQ -->
<section class="block">
<h2 data-i18n="3d.notes">Notes</h2>
<div class="faq">
<details>
<summary data-i18n="3d.is_it_really_free_and_keyless">Is it really free and keyless?</summary>
<p data-i18n="3d.yes_no_account_no_api_key_no">Yes. No account, no API key, no wallet. Free endpoints have a generous per-IP rate limit that only blunts abuse. The free draft generation tier runs on a shared GPU lane — it's honest about being a prototyping tier (draft geometry, GLB only, no rigging).</p>
</details>
<details>
<summary data-i18n="3d.what_formats_are_supported">What formats are supported?</summary>
<p data-i18n-html="3d.output_is_binary_gltf_glb_the_web">Output is binary glTF (GLB) — the web-standard 3D container every engine and viewer reads. Inspection accepts <code>.glb</code> and <code>.gltf</code> from any public URL, up to the documented size cap.</p>
</details>
<details>
<summary data-i18n="3d.how_do_agents_pay_for_the_pro">How do agents pay for the pro tiers?</summary>
<p data-i18n-html="3d.programmatically_over_x402_http_402_usdc_on">Programmatically, over <a href="/pay">x402</a> (HTTP 402 + USDC on Base or Solana). No key exchange — an agent just retries the request with a payment header. See the paid <a href="/bazaar">x402 Bazaar</a> for the full catalog.</p>
</details>
</div>
</section>
<footer class="foot">
Built for agents. Machine-readable spec: <a href="/api/3d/openapi.json" data-i18n="3d.api_3d_openapi_json">/api/3d/openapi.json</a> · Discovery: <a href="/api/3d" data-i18n="3d.api_3d">/api/3d</a> · Paid catalog: <a href="/bazaar" data-i18n="3d.x402_bazaar">x402 Bazaar</a>
</footer>
</main>
<script src="/brand.js" defer></script>
<script type="module">
// Live wiring. Everything degrades gracefully: in local dev, /api/* proxies
// to production, so these calls hit the real endpoints. If a call fails
// (offline, pre-deploy), we fall back to the known real catalog shape so the
// page is never broken — but we mark the live badge honestly.
const $ = (s) => document.querySelector(s);
const esc = (s) => String(s ?? '').replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
// Real, known catalog shape — the fallback if the live index can't be reached.
const FALLBACK = [
{ methods: ['POST'], path: '/api/3d/generate', title: 'Text → 3D (free draft)', summary: 'Turn a text prompt into a real textured GLB. Draft tier, GLB only, no rigging.', free: true },
{ methods: ['GET', 'POST'], path: '/api/3d/inspect', title: '3D Model Inspect & Validate', summary: 'Validate a glTF/GLB and return structural stats + a prioritized optimization plan.', free: true },
];
function renderRows(endpoints) {
const rows = (endpoints && endpoints.length ? endpoints : FALLBACK).map((e) => {
const verbs = (e.methods || ['GET']).map((m) => `<span class="verb">${esc(m)}</span>`).join(' ');
return `<tr>
<td>${verbs}</td>
<td><code>${esc(e.path)}</code></td>
<td>${esc(e.title || e.slug || '')}${e.summary ? `<br><span style="color:var(--ink-dim)">${esc(e.summary)}</span>` : ''}</td>
<td><span class="tier-tag">Free</span></td>
</tr>`;
}).join('');
$('#epRows').innerHTML = rows;
$('#statEndpoints').textContent = String((endpoints && endpoints.length) || FALLBACK.length);
}
function setLive(up) {
const b = $('#liveBadge');
b.classList.toggle('up', up);
b.classList.toggle('down', !up);
$('#liveText').textContent = up ? 'live' : 'offline';
}
// 1) Discover the catalog for the endpoint table + stat.
(async () => {
try {
const r = await fetch('/api/3d', { headers: { accept: 'application/json' } });
if (!r.ok) throw new Error('status ' + r.status);
const data = await r.json();
renderRows(data.endpoints);
setLive(true);
} catch {
renderRows(null); // real fallback shape
setLive(false);
}
})();
// 2) Run inspect live on demand.
const GLB = 'https://three.ws/avatars/cesium-man.glb';
$('#runInspect').addEventListener('click', async () => {
const btn = $('#runInspect');
const out = $('#inspectOut');
const status = $('#inspectStatus');
btn.disabled = true;
status.textContent = 'calling /api/3d/inspect …';
const t0 = performance.now();
try {
const r = await fetch('/api/3d/inspect?url=' + encodeURIComponent(GLB), { headers: { accept: 'application/json' } });
const ms = Math.round(performance.now() - t0);
const data = await r.json();
out.textContent = JSON.stringify(data, null, 2);
status.textContent = `${r.status} · ${ms} ms`;
} catch (err) {
out.textContent = '// Live call failed (the API may not be deployed to this environment yet).\n// The endpoint is https://three.ws/api/3d/inspect?url=<glb>\n// ' + esc(err.message);
status.textContent = 'offline';
} finally {
btn.disabled = false;
}
});
</script>
<!-- Runtime i18n: detects locale, swaps annotated copy, mounts <lang-switcher>. -->
<script type="module" src="/i18n.js"></script>
</body>
</html>