| <!DOCTYPE html> |
| <html lang="en" class="antialiased scroll-smooth"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>APIarium | API Documentation</title> |
| <meta name="description" content="APIarium API documentation β OpenAI-compatible endpoints, supported models (Veo 3 video, Gemini 3 image, GPT, Claude, etc.), and code examples."> |
|
|
| |
| <link rel="preconnect" href="https://fonts.googleapis.com"> |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet"> |
|
|
| |
| <script src="https://cdn.tailwindcss.com"></script> |
| <script> |
| tailwind.config = { |
| theme: { |
| extend: { |
| fontFamily: { |
| sans: ['Inter', 'sans-serif'], |
| mono: ['JetBrains Mono', 'monospace'], |
| }, |
| colors: { |
| gray: { |
| 900: '#0A0A0A', |
| 800: '#171717', |
| 700: '#262626', |
| 600: '#404040', |
| } |
| } |
| } |
| } |
| } |
| </script> |
|
|
| <style> |
| body { |
| background-color: #000000; |
| color: #ededed; |
| } |
| |
| ::-webkit-scrollbar { width: 8px; height: 8px; } |
| ::-webkit-scrollbar-track { background: transparent; } |
| ::-webkit-scrollbar-thumb { background: #262626; border-radius: 4px; } |
| ::-webkit-scrollbar-thumb:hover { background: #404040; } |
| |
| @keyframes slideUp { |
| from { transform: translate(-50%, 100%); opacity: 0; } |
| to { transform: translate(-50%, 0); opacity: 1; } |
| } |
| .toast-enter { animation: slideUp 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards; } |
| |
| .bg-grid-pattern { |
| background-image: linear-gradient(to right, #ffffff05 1px, transparent 1px), |
| linear-gradient(to bottom, #ffffff05 1px, transparent 1px); |
| background-size: 40px 40px; |
| mask-image: radial-gradient(circle at center, black, transparent 80%); |
| -webkit-mask-image: radial-gradient(circle at center, black, transparent 80%); |
| } |
| |
| |
| pre { |
| font-family: 'JetBrains Mono', monospace; |
| font-size: 12.5px; |
| line-height: 1.55; |
| white-space: pre; |
| overflow-x: auto; |
| color: #d4d4d4; |
| } |
| pre .tk-key { color: #93c5fd; } |
| pre .tk-str { color: #86efac; } |
| pre .tk-num { color: #fbbf24; } |
| pre .tk-bool { color: #f0abfc; } |
| pre .tk-cmt { color: #6b7280; font-style: italic; } |
| pre .tk-fn { color: #fbbf24; } |
| pre .tk-kw { color: #c4b5fd; } |
| |
| |
| section[id] { scroll-margin-top: 80px; } |
| |
| |
| .toc-link.active { |
| color: #fff; |
| background: rgba(255,255,255,0.05); |
| border-left-color: #fbbf24; |
| } |
| </style> |
| </head> |
| <body class="min-h-screen flex flex-col relative"> |
|
|
| <div class="absolute inset-0 bg-grid-pattern pointer-events-none z-0"></div> |
|
|
| <header class="sticky top-0 z-50 bg-black/80 backdrop-blur-md border-b border-white/10"> |
| <div class="max-w-7xl mx-auto px-6 h-16 flex items-center justify-between"> |
| <a href="/" class="flex items-center gap-3 group"> |
| <span class="text-2xl" aria-label="APIarium">π</span> |
| <span class="font-semibold text-lg tracking-tight group-hover:text-amber-300 transition-colors">APIarium</span> |
| <span class="ml-2 px-2 py-0.5 text-[10px] font-mono font-medium bg-white/10 text-white rounded-full border border-white/20">v1.0.0</span> |
| </a> |
| <nav class="hidden sm:flex items-center gap-6 text-sm font-medium text-gray-400"> |
| <a href="/" class="hover:text-white transition-colors">Home</a> |
| <a href="/docs" class="text-white">Documentation</a> |
| <a href="/openapi-docs" class="hover:text-white transition-colors">Swagger</a> |
| </nav> |
| </div> |
| </header> |
|
|
| <main class="flex-grow w-full max-w-7xl mx-auto px-6 py-12 relative z-10"> |
|
|
| |
| <section class="pb-10 border-b border-gray-800 mb-10"> |
| <div class="flex items-center gap-2 text-xs font-mono uppercase tracking-wider text-amber-400 mb-3"> |
| <span class="w-1.5 h-1.5 rounded-full bg-amber-400"></span> |
| Reference |
| </div> |
| <h1 class="text-4xl sm:text-5xl font-semibold tracking-tight text-white mb-4 leading-tight"> |
| API Documentation |
| </h1> |
| <p class="text-lg text-gray-400 leading-relaxed max-w-3xl"> |
| Everything you need to integrate with APIarium β an OpenAI-compatible gateway that fans out to GPT, Claude, Gemini, DeepSeek, Veo 3 video, Gemini 3 image and more, behind a single Bearer token. |
| </p> |
|
|
| <div class="flex flex-wrap gap-2 mt-6"> |
| <span class="px-3 py-1 text-xs font-mono bg-blue-500/10 text-blue-300 border border-blue-500/20 rounded-md">OpenAI-compatible</span> |
| <span class="px-3 py-1 text-xs font-mono bg-emerald-500/10 text-emerald-300 border border-emerald-500/20 rounded-md">Streaming</span> |
| <span class="px-3 py-1 text-xs font-mono bg-amber-500/10 text-amber-300 border border-amber-500/20 rounded-md">Veo 3 video</span> |
| <span class="px-3 py-1 text-xs font-mono bg-fuchsia-500/10 text-fuchsia-300 border border-fuchsia-500/20 rounded-md">Gemini 3 image</span> |
| <span class="px-3 py-1 text-xs font-mono bg-white/5 text-gray-300 border border-white/10 rounded-md">IP-bound keys</span> |
| </div> |
| </section> |
|
|
| <div class="grid grid-cols-1 lg:grid-cols-[220px_1fr] gap-12"> |
|
|
| |
| <aside class="hidden lg:block"> |
| <nav class="sticky top-24 space-y-1 text-sm"> |
| <p class="text-[10px] font-mono uppercase tracking-wider text-gray-500 mb-3 px-3">On this page</p> |
| <a href="#quickstart" class="toc-link block px-3 py-1.5 border-l border-gray-800 text-gray-400 hover:text-white transition-colors">Quick start</a> |
| <a href="#auth" class="toc-link block px-3 py-1.5 border-l border-gray-800 text-gray-400 hover:text-white transition-colors">Authentication</a> |
| <a href="#base-url" class="toc-link block px-3 py-1.5 border-l border-gray-800 text-gray-400 hover:text-white transition-colors">Base URL</a> |
| <a href="#endpoints" class="toc-link block px-3 py-1.5 border-l border-gray-800 text-gray-400 hover:text-white transition-colors">Endpoints</a> |
| <a href="#models" class="toc-link block px-3 py-1.5 border-l border-gray-800 text-gray-400 hover:text-white transition-colors">Models catalog</a> |
| <a href="#chat" class="toc-link block px-3 py-1.5 border-l border-gray-800 text-gray-400 hover:text-white transition-colors">Chat completions</a> |
| <a href="#veo3" class="toc-link block px-3 py-1.5 border-l border-gray-800 text-gray-400 hover:text-white transition-colors">Veo 3 β video</a> |
| <a href="#gemini3-image" class="toc-link block px-3 py-1.5 border-l border-gray-800 text-gray-400 hover:text-white transition-colors">Gemini 3 β image</a> |
| <a href="#gemini3-text" class="toc-link block px-3 py-1.5 border-l border-gray-800 text-gray-400 hover:text-white transition-colors">Gemini 3 β text</a> |
| <a href="#gpt-image" class="toc-link block px-3 py-1.5 border-l border-gray-800 text-gray-400 hover:text-white transition-colors">gpt-image-*</a> |
| <a href="#errors" class="toc-link block px-3 py-1.5 border-l border-gray-800 text-gray-400 hover:text-white transition-colors">Errors & limits</a> |
| <a href="#sdks" class="toc-link block px-3 py-1.5 border-l border-gray-800 text-gray-400 hover:text-white transition-colors">SDK examples</a> |
| </nav> |
| </aside> |
|
|
| |
| <article class="space-y-16 min-w-0"> |
|
|
| |
| <section id="quickstart"> |
| <h2 class="text-2xl font-semibold text-white mb-2">Quick start</h2> |
| <p class="text-gray-400 mb-6">Three steps and you're sending requests.</p> |
|
|
| <ol class="space-y-5"> |
| <li class="flex gap-4"> |
| <span class="shrink-0 w-7 h-7 rounded-full bg-amber-500/10 border border-amber-500/30 text-amber-300 text-xs font-mono flex items-center justify-center">1</span> |
| <div> |
| <p class="text-sm font-medium text-white mb-1">Grab an API key</p> |
| <p class="text-sm text-gray-400">Visit <a class="text-amber-300 hover:underline" href="/">the homepage</a> β a key is automatically generated for your IP. Keys are bound to that IP.</p> |
| </div> |
| </li> |
| <li class="flex gap-4"> |
| <span class="shrink-0 w-7 h-7 rounded-full bg-amber-500/10 border border-amber-500/30 text-amber-300 text-xs font-mono flex items-center justify-center">2</span> |
| <div> |
| <p class="text-sm font-medium text-white mb-1">Point your OpenAI SDK at us</p> |
| <p class="text-sm text-gray-400">Override <code class="font-mono bg-gray-900 px-1.5 py-0.5 rounded text-amber-300">base_url</code> and you're done β everything else is identical to OpenAI.</p> |
| </div> |
| </li> |
| <li class="flex gap-4"> |
| <span class="shrink-0 w-7 h-7 rounded-full bg-amber-500/10 border border-amber-500/30 text-amber-300 text-xs font-mono flex items-center justify-center">3</span> |
| <div> |
| <p class="text-sm font-medium text-white mb-1">Pick any model</p> |
| <p class="text-sm text-gray-400">From <code class="font-mono bg-gray-900 px-1.5 py-0.5 rounded text-amber-300">gpt-5</code> to <code class="font-mono bg-gray-900 px-1.5 py-0.5 rounded text-amber-300">veo-3</code>. See the <a class="text-amber-300 hover:underline" href="#models">full catalog</a>.</p> |
| </div> |
| </li> |
| </ol> |
|
|
| <div class="mt-8 bg-gray-900 border border-gray-800 rounded-xl overflow-hidden"> |
| <div class="px-4 py-2.5 border-b border-gray-800 flex items-center justify-between"> |
| <span class="text-[10px] font-mono uppercase tracking-wider text-gray-500">curl Β· Generate a Veo 3 video</span> |
| <button class="copy-btn text-[10px] font-mono uppercase tracking-wider text-gray-500 hover:text-white transition-colors px-2 py-1 rounded border border-gray-800">Copy</button> |
| </div> |
| <pre class="px-4 py-4"><span class="tk-cmt"># Returns OpenAI-shaped { "data": [{ "url": "https://β¦/video.mp4" }] }</span> |
| curl <span class="tk-str">"<span id="base-url-1">https://your-apiarium.example.com</span>/v1/videos/generations"</span> \ |
| -H <span class="tk-str">"Authorization: Bearer api-xxxxxxxxxxxxxxxx"</span> \ |
| -H <span class="tk-str">"Content-Type: application/json"</span> \ |
| -d <span class="tk-str">'{ |
| "model": "veo-3", |
| "prompt": "a red apple rotating on white, studio lighting", |
| "duration_seconds": "8", |
| "resolution": "1080p", |
| "size": "16:9" |
| }'</span></pre> |
| </div> |
| </section> |
|
|
| |
| <section id="auth"> |
| <h2 class="text-2xl font-semibold text-white mb-2">Authentication</h2> |
| <p class="text-gray-400 mb-4">Every request to <code class="font-mono bg-gray-900 px-1.5 py-0.5 rounded text-amber-300">/v1/*</code> needs a Bearer token.</p> |
| <div class="bg-gray-900 border border-gray-800 rounded-xl p-4 mb-4"> |
| <pre><span class="tk-kw">Authorization:</span> Bearer <span class="tk-str">api-xxxxxxxxxxxxxxxx</span></pre> |
| </div> |
| <ul class="text-sm text-gray-400 space-y-2 leading-relaxed"> |
| <li>π Each key is <strong class="text-white font-medium">bound to one IP address</strong>. Requests from a different network are rejected with HTTP 401.</li> |
| <li>β±οΈ Default rate limit: <strong class="text-white font-medium">4 RPM</strong> per key. The master key bypasses limits.</li> |
| <li>πͺͺ Lose your key? Visit <a class="text-amber-300 hover:underline" href="/">the homepage</a> again β your IP will get the same key back.</li> |
| </ul> |
| </section> |
|
|
| |
| <section id="base-url"> |
| <h2 class="text-2xl font-semibold text-white mb-2">Base URL</h2> |
| <p class="text-gray-400 mb-4">All endpoints below are relative to:</p> |
| <div class="bg-gray-900 border border-gray-800 rounded-xl p-4 flex items-center justify-between"> |
| <code class="font-mono text-sm text-amber-300" id="base-url-display">https://your-apiarium.example.com</code> |
| <button id="copy-base-url" class="text-[10px] font-mono uppercase tracking-wider text-gray-500 hover:text-white transition-colors px-2 py-1 rounded border border-gray-800">Copy</button> |
| </div> |
| <p class="text-xs text-gray-500 mt-2 font-mono">β this page auto-detects the running host</p> |
| </section> |
|
|
| |
| <section id="endpoints"> |
| <h2 class="text-2xl font-semibold text-white mb-6">Endpoints</h2> |
| <div class="bg-gray-900 border border-gray-800 rounded-xl overflow-hidden"> |
| <table class="w-full text-sm"> |
| <thead class="bg-gray-900/50 border-b border-gray-800 text-[10px] font-mono uppercase tracking-wider text-gray-500"> |
| <tr> |
| <th class="text-left px-4 py-3 w-16">Method</th> |
| <th class="text-left px-4 py-3">Path</th> |
| <th class="text-left px-4 py-3">Purpose</th> |
| </tr> |
| </thead> |
| <tbody class="divide-y divide-gray-800"> |
| <tr><td class="px-4 py-3"><span class="px-1.5 py-0.5 text-[10px] font-mono font-bold bg-blue-500/10 text-blue-400 rounded">GET</span></td> <td class="px-4 py-3 font-mono text-gray-300">/v1/models</td> <td class="px-4 py-3 text-gray-400">List all available model aliases</td></tr> |
| <tr><td class="px-4 py-3"><span class="px-1.5 py-0.5 text-[10px] font-mono font-bold bg-amber-500/10 text-amber-400 rounded">POST</span></td><td class="px-4 py-3 font-mono text-gray-300">/v1/chat/completions</td> <td class="px-4 py-3 text-gray-400">Chat with any text/image/video model</td></tr> |
| <tr><td class="px-4 py-3"><span class="px-1.5 py-0.5 text-[10px] font-mono font-bold bg-amber-500/10 text-amber-400 rounded">POST</span></td><td class="px-4 py-3 font-mono text-gray-300">/v1/completions</td> <td class="px-4 py-3 text-gray-400">Legacy text completions</td></tr> |
| <tr><td class="px-4 py-3"><span class="px-1.5 py-0.5 text-[10px] font-mono font-bold bg-amber-500/10 text-amber-400 rounded">POST</span></td><td class="px-4 py-3 font-mono text-gray-300">/v1/images/generations</td><td class="px-4 py-3 text-gray-400">Image generation (gpt-image-*, gemini-3-image*)</td></tr> |
| <tr><td class="px-4 py-3"><span class="px-1.5 py-0.5 text-[10px] font-mono font-bold bg-amber-500/10 text-amber-400 rounded">POST</span></td><td class="px-4 py-3 font-mono text-gray-300">/v1/videos/generations</td><td class="px-4 py-3 text-gray-400">Video generation (veo-3, veo-3-fast, veo-3.1-fast)</td></tr> |
| <tr><td class="px-4 py-3"><span class="px-1.5 py-0.5 text-[10px] font-mono font-bold bg-blue-500/10 text-blue-400 rounded">GET</span></td> <td class="px-4 py-3 font-mono text-gray-300">/v1/keys/me</td> <td class="px-4 py-3 text-gray-400">Get / auto-generate the key bound to your IP</td></tr> |
| <tr><td class="px-4 py-3"><span class="px-1.5 py-0.5 text-[10px] font-mono font-bold bg-blue-500/10 text-blue-400 rounded">GET</span></td> <td class="px-4 py-3 font-mono text-gray-300">/health</td> <td class="px-4 py-3 text-gray-400">Health check</td></tr> |
| </tbody> |
| </table> |
| </div> |
| </section> |
|
|
| |
| <section id="models"> |
| <h2 class="text-2xl font-semibold text-white mb-2">Models catalog</h2> |
| <p class="text-gray-400 mb-6">Live snapshot from <code class="font-mono bg-gray-900 px-1.5 py-0.5 rounded text-amber-300">GET /v1/models</code>. Click any tile to copy the model id.</p> |
|
|
| <div class="mb-4 flex items-center justify-between"> |
| <div class="flex flex-wrap gap-2 text-xs font-mono"> |
| <button data-filter="all" class="model-filter px-3 py-1.5 rounded-md border border-gray-700 bg-gray-900 text-gray-200 hover:border-amber-500/50 transition-colors active-filter">All</button> |
| <button data-filter="chat" class="model-filter px-3 py-1.5 rounded-md border border-gray-800 bg-gray-900/50 text-gray-400 hover:border-gray-600 hover:text-white transition-colors">Chat / text</button> |
| <button data-filter="image" class="model-filter px-3 py-1.5 rounded-md border border-gray-800 bg-gray-900/50 text-gray-400 hover:border-gray-600 hover:text-white transition-colors">Image</button> |
| <button data-filter="video" class="model-filter px-3 py-1.5 rounded-md border border-gray-800 bg-gray-900/50 text-gray-400 hover:border-gray-600 hover:text-white transition-colors">Video</button> |
| </div> |
| <span id="docs-models-badge" class="text-xs text-gray-500 font-mono">Loadingβ¦</span> |
| </div> |
|
|
| <div id="docs-models-grid" class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-3"> |
| <div class="col-span-full py-10 text-center text-gray-500 text-sm font-mono">Loading model registryβ¦</div> |
| </div> |
| </section> |
|
|
| |
| <section id="chat"> |
| <h2 class="text-2xl font-semibold text-white mb-2">Chat completions</h2> |
| <p class="text-gray-400 mb-6">Drop-in replacement for <code class="font-mono bg-gray-900 px-1.5 py-0.5 rounded text-amber-300">POST https://api.openai.com/v1/chat/completions</code>. Streaming via <code class="font-mono bg-gray-900 px-1.5 py-0.5 rounded text-amber-300">"stream": true</code> works out of the box.</p> |
|
|
| <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> |
| <div class="bg-gray-900 border border-gray-800 rounded-xl overflow-hidden"> |
| <div class="px-4 py-2.5 border-b border-gray-800 text-[10px] font-mono uppercase tracking-wider text-gray-500">Request body</div> |
| <pre class="px-4 py-4">{ |
| <span class="tk-key">"model"</span>: <span class="tk-str">"gpt-5"</span>, |
| <span class="tk-key">"messages"</span>: [ |
| {<span class="tk-key">"role"</span>: <span class="tk-str">"user"</span>, <span class="tk-key">"content"</span>: <span class="tk-str">"Hello"</span>} |
| ], |
| <span class="tk-key">"stream"</span>: <span class="tk-bool">true</span>, |
| <span class="tk-key">"temperature"</span>: <span class="tk-num">0.7</span> |
| }</pre> |
| </div> |
| <div class="bg-gray-900 border border-gray-800 rounded-xl overflow-hidden"> |
| <div class="px-4 py-2.5 border-b border-gray-800 text-[10px] font-mono uppercase tracking-wider text-gray-500">Response (non-stream)</div> |
| <pre class="px-4 py-4">{ |
| <span class="tk-key">"id"</span>: <span class="tk-str">"chatcmpl-β¦"</span>, |
| <span class="tk-key">"object"</span>: <span class="tk-str">"chat.completion"</span>, |
| <span class="tk-key">"model"</span>: <span class="tk-str">"gpt-5"</span>, |
| <span class="tk-key">"choices"</span>: [{ |
| <span class="tk-key">"index"</span>: <span class="tk-num">0</span>, |
| <span class="tk-key">"message"</span>: { |
| <span class="tk-key">"role"</span>: <span class="tk-str">"assistant"</span>, |
| <span class="tk-key">"content"</span>: <span class="tk-str">"Hi! How can I help?"</span> |
| }, |
| <span class="tk-key">"finish_reason"</span>: <span class="tk-str">"stop"</span> |
| }] |
| }</pre> |
| </div> |
| </div> |
| </section> |
|
|
| |
| <section id="veo3"> |
| <div class="flex items-center gap-2 text-xs font-mono uppercase tracking-wider text-amber-400 mb-2"> |
| <span class="w-1.5 h-1.5 rounded-full bg-amber-400"></span> |
| Video generation |
| </div> |
| <h2 class="text-2xl font-semibold text-white mb-2">Veo 3 β video</h2> |
| <p class="text-gray-400 mb-6"> |
| Dedicated endpoint: |
| <code class="font-mono bg-gray-900 px-1.5 py-0.5 rounded text-amber-300">POST /v1/videos/generations</code>. |
| Models: |
| <code class="font-mono bg-gray-900 px-1.5 py-0.5 rounded text-amber-300">veo-3</code>, |
| <code class="font-mono bg-gray-900 px-1.5 py-0.5 rounded text-amber-300">veo-3-fast</code>, |
| <code class="font-mono bg-gray-900 px-1.5 py-0.5 rounded text-amber-300">veo-3.1-fast</code>. |
| Returns an OpenAI-style payload: <code class="font-mono">{"data": [{"url": "https://β¦/video.mp4"}]}</code>. |
| </p> |
| <p class="text-sm text-gray-500 mb-6"> |
| You can also send <code class="font-mono text-amber-300">model: "veo-3"</code> to |
| <code class="font-mono text-amber-300">/v1/chat/completions</code> β the assistant message will then |
| contain the bare MP4 URL β but the dedicated endpoint is more ergonomic. |
| </p> |
|
|
| <h3 class="text-sm font-medium text-white uppercase tracking-wider mb-3">Supported parameters</h3> |
| <div class="bg-gray-900 border border-gray-800 rounded-xl overflow-hidden mb-6"> |
| <table class="w-full text-sm"> |
| <thead class="bg-gray-900/50 border-b border-gray-800 text-[10px] font-mono uppercase tracking-wider text-gray-500"> |
| <tr> |
| <th class="text-left px-4 py-3">Field</th> |
| <th class="text-left px-4 py-3">Type</th> |
| <th class="text-left px-4 py-3">Default</th> |
| <th class="text-left px-4 py-3">Notes</th> |
| </tr> |
| </thead> |
| <tbody class="divide-y divide-gray-800 text-gray-300"> |
| <tr> |
| <td class="px-4 py-3 font-mono text-amber-300">messages[].content</td> |
| <td class="px-4 py-3 text-gray-400 font-mono">string</td> |
| <td class="px-4 py-3 text-gray-500 font-mono">β</td> |
| <td class="px-4 py-3 text-gray-400">The video prompt. We flatten all user/system turns into one string.</td> |
| </tr> |
| <tr> |
| <td class="px-4 py-3 font-mono text-amber-300">duration_seconds</td> |
| <td class="px-4 py-3 text-gray-400 font-mono">"4" | "6" | "8"</td> |
| <td class="px-4 py-3 text-gray-500 font-mono">"8"</td> |
| <td class="px-4 py-3 text-gray-400">Even seconds only. Longer = more credits.</td> |
| </tr> |
| <tr> |
| <td class="px-4 py-3 font-mono text-amber-300">resolution</td> |
| <td class="px-4 py-3 text-gray-400 font-mono">"720p" | "1080p"</td> |
| <td class="px-4 py-3 text-gray-500 font-mono">"720p"</td> |
| <td class="px-4 py-3 text-gray-400">1080p costs more.</td> |
| </tr> |
| <tr> |
| <td class="px-4 py-3 font-mono text-amber-300">aspect_ratio</td> |
| <td class="px-4 py-3 text-gray-400 font-mono">"16:9" | "9:16"</td> |
| <td class="px-4 py-3 text-gray-500 font-mono">"16:9"</td> |
| <td class="px-4 py-3 text-gray-400">Landscape or portrait.</td> |
| </tr> |
| <tr> |
| <td class="px-4 py-3 font-mono text-amber-300">image_url</td> |
| <td class="px-4 py-3 text-gray-400 font-mono">string</td> |
| <td class="px-4 py-3 text-gray-500 font-mono">β</td> |
| <td class="px-4 py-3 text-gray-400">Image-to-video: seed frame that gets animated.</td> |
| </tr> |
| </tbody> |
| </table> |
| </div> |
|
|
| <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> |
| <div class="bg-gray-900 border border-gray-800 rounded-xl overflow-hidden"> |
| <div class="px-4 py-2.5 border-b border-gray-800 text-[10px] font-mono uppercase tracking-wider text-gray-500">POST /v1/videos/generations Β· request</div> |
| <pre class="px-4 py-4">{ |
| <span class="tk-key">"model"</span>: <span class="tk-str">"veo-3"</span>, |
| <span class="tk-key">"prompt"</span>: <span class="tk-str">"a red apple rotating on white, studio lighting"</span>, |
| <span class="tk-key">"duration_seconds"</span>: <span class="tk-str">"8"</span>, |
| <span class="tk-key">"resolution"</span>: <span class="tk-str">"1080p"</span>, |
| <span class="tk-key">"size"</span>: <span class="tk-str">"16:9"</span> |
| }</pre> |
| </div> |
| <div class="bg-gray-900 border border-gray-800 rounded-xl overflow-hidden"> |
| <div class="px-4 py-2.5 border-b border-gray-800 text-[10px] font-mono uppercase tracking-wider text-gray-500">Response</div> |
| <pre class="px-4 py-4">{ |
| <span class="tk-key">"created"</span>: <span class="tk-num">1779570172</span>, |
| <span class="tk-key">"data"</span>: [{ |
| <span class="tk-key">"url"</span>: <span class="tk-str">"https://β¦/video.mp4"</span>, |
| <span class="tk-key">"revised_prompt"</span>: <span class="tk-str">"a red apple rotating on whiteβ¦"</span> |
| }] |
| }</pre> |
| </div> |
| </div> |
|
|
| <div class="mt-4 p-4 rounded-xl border border-amber-500/20 bg-amber-500/5 text-sm text-amber-200"> |
| β οΈ Video generation takes <strong>30 s β 2 min</strong>. Set an HTTP timeout of at least <strong>10 minutes</strong> client-side. Veo 3's safety filter may reject some prompts β you'll get a <code class="font-mono bg-amber-500/10 px-1 rounded">400</code> with a clear error. |
| </div> |
| </section> |
|
|
| |
| <section id="gemini3-image"> |
| <div class="flex items-center gap-2 text-xs font-mono uppercase tracking-wider text-fuchsia-300 mb-2"> |
| <span class="w-1.5 h-1.5 rounded-full bg-fuchsia-300"></span> |
| Image generation |
| </div> |
| <h2 class="text-2xl font-semibold text-white mb-2">Gemini 3 β image</h2> |
| <p class="text-gray-400 mb-6"> |
| Dedicated endpoint: |
| <code class="font-mono bg-gray-900 px-1.5 py-0.5 rounded text-amber-300">POST /v1/images/generations</code>. |
| Models: |
| <code class="font-mono bg-gray-900 px-1.5 py-0.5 rounded text-amber-300">gemini-3-image</code>, |
| <code class="font-mono bg-gray-900 px-1.5 py-0.5 rounded text-amber-300">gemini-3-flash-image</code>, |
| <code class="font-mono bg-gray-900 px-1.5 py-0.5 rounded text-amber-300">gemini-3.1-flash-image</code>. |
| Append <code class="font-mono bg-gray-900 px-1.5 py-0.5 rounded text-amber-300">-pro</code> to any of them for the higher-quality variant (~2 min). |
| </p> |
|
|
| <h3 class="text-sm font-medium text-white uppercase tracking-wider mb-3">Supported parameters</h3> |
| <div class="bg-gray-900 border border-gray-800 rounded-xl overflow-hidden mb-6"> |
| <table class="w-full text-sm"> |
| <thead class="bg-gray-900/50 border-b border-gray-800 text-[10px] font-mono uppercase tracking-wider text-gray-500"> |
| <tr> |
| <th class="text-left px-4 py-3">Field</th> |
| <th class="text-left px-4 py-3">Type</th> |
| <th class="text-left px-4 py-3">Default</th> |
| <th class="text-left px-4 py-3">Notes</th> |
| </tr> |
| </thead> |
| <tbody class="divide-y divide-gray-800 text-gray-300"> |
| <tr><td class="px-4 py-3 font-mono text-amber-300">prompt</td> <td class="px-4 py-3 text-gray-400 font-mono">string</td><td class="px-4 py-3 text-gray-500 font-mono">β</td> <td class="px-4 py-3 text-gray-400">Image description.</td></tr> |
| <tr><td class="px-4 py-3 font-mono text-amber-300">size</td> <td class="px-4 py-3 text-gray-400 font-mono">aspect or WxH</td><td class="px-4 py-3 text-gray-500 font-mono">"1024x1024"</td><td class="px-4 py-3 text-gray-400">Accepts ratios (1:1, 16:9, 9:16, 4:3, 3:4) or OpenAI sizes (1024x1024, 1792x1024, β¦).</td></tr> |
| <tr><td class="px-4 py-3 font-mono text-amber-300">n</td> <td class="px-4 py-3 text-gray-400 font-mono">integer</td><td class="px-4 py-3 text-gray-500 font-mono">1</td> <td class="px-4 py-3 text-gray-400">Repeated entries in <code class="font-mono">data[]</code>; upstream still produces one URL.</td></tr> |
| </tbody> |
| </table> |
| </div> |
|
|
| <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> |
| <div class="bg-gray-900 border border-gray-800 rounded-xl overflow-hidden"> |
| <div class="px-4 py-2.5 border-b border-gray-800 text-[10px] font-mono uppercase tracking-wider text-gray-500">POST /v1/images/generations Β· request</div> |
| <pre class="px-4 py-4">{ |
| <span class="tk-key">"model"</span>: <span class="tk-str">"gemini-3-image"</span>, |
| <span class="tk-key">"prompt"</span>: <span class="tk-str">"a glass of orange juice on a sunny kitchen counter"</span>, |
| <span class="tk-key">"size"</span>: <span class="tk-str">"1:1"</span> |
| }</pre> |
| </div> |
| <div class="bg-gray-900 border border-gray-800 rounded-xl overflow-hidden"> |
| <div class="px-4 py-2.5 border-b border-gray-800 text-[10px] font-mono uppercase tracking-wider text-gray-500">Response</div> |
| <pre class="px-4 py-4">{ |
| <span class="tk-key">"created"</span>: <span class="tk-num">1779570172</span>, |
| <span class="tk-key">"data"</span>: [{ |
| <span class="tk-key">"url"</span>: <span class="tk-str">"https://β¦/image.png"</span>, |
| <span class="tk-key">"revised_prompt"</span>: <span class="tk-str">"a glass of orange juiceβ¦"</span> |
| }] |
| }</pre> |
| </div> |
| </div> |
| </section> |
|
|
| |
| <section id="gemini3-text"> |
| <h2 class="text-2xl font-semibold text-white mb-2">Gemini 3 β text (RAG)</h2> |
| <p class="text-gray-400 mb-4"> |
| Models: <code class="font-mono bg-gray-900 px-1.5 py-0.5 rounded text-amber-300">gemini-3</code>, |
| <code class="font-mono bg-gray-900 px-1.5 py-0.5 rounded text-amber-300">gemini-3-flash</code>, |
| <code class="font-mono bg-gray-900 px-1.5 py-0.5 rounded text-amber-300">gemini-3-pro</code>. |
| </p> |
| <p class="text-sm text-gray-500 mb-6"> |
| These route to Gemini Hub's RAG-grounded <code class="font-mono">/query</code> endpoint. For free-form chat without a knowledge base, prefer |
| <code class="font-mono text-amber-300">gemini-2.5-pro</code>, <code class="font-mono text-amber-300">gemini-3.1-pro</code> or one of the GPT/Claude aliases. |
| </p> |
| </section> |
|
|
| |
| <section id="gpt-image"> |
| <h2 class="text-2xl font-semibold text-white mb-2">gpt-image-1 / gpt-image-2</h2> |
| <p class="text-gray-400 mb-6"> |
| Same endpoint as Gemini 3 image: |
| <code class="font-mono bg-gray-900 px-1.5 py-0.5 rounded text-amber-300">POST /v1/images/generations</code>. |
| Just swap the <code class="font-mono text-amber-300">model</code> id. |
| </p> |
|
|
| <div class="bg-gray-900 border border-gray-800 rounded-xl overflow-hidden"> |
| <div class="px-4 py-2.5 border-b border-gray-800 text-[10px] font-mono uppercase tracking-wider text-gray-500">Request</div> |
| <pre class="px-4 py-4">{ |
| <span class="tk-key">"model"</span>: <span class="tk-str">"gpt-image-1"</span>, |
| <span class="tk-key">"prompt"</span>: <span class="tk-str">"a watercolor of Istanbul rooftops"</span>, |
| <span class="tk-key">"n"</span>: <span class="tk-num">1</span>, |
| <span class="tk-key">"size"</span>: <span class="tk-str">"1024x1024"</span> |
| }</pre> |
| </div> |
| </section> |
|
|
| |
| <section id="errors"> |
| <h2 class="text-2xl font-semibold text-white mb-2">Errors & limits</h2> |
| <div class="bg-gray-900 border border-gray-800 rounded-xl overflow-hidden"> |
| <table class="w-full text-sm"> |
| <thead class="bg-gray-900/50 border-b border-gray-800 text-[10px] font-mono uppercase tracking-wider text-gray-500"> |
| <tr> |
| <th class="text-left px-4 py-3 w-20">Status</th> |
| <th class="text-left px-4 py-3">Meaning</th> |
| <th class="text-left px-4 py-3">Fix</th> |
| </tr> |
| </thead> |
| <tbody class="divide-y divide-gray-800 text-gray-300"> |
| <tr><td class="px-4 py-3 font-mono text-red-400">401</td> <td class="px-4 py-3">IP mismatch or missing/invalid key</td><td class="px-4 py-3 text-gray-400">Hit <a class="text-amber-300 hover:underline" href="/v1/keys/me">/v1/keys/me</a> from your real IP.</td></tr> |
| <tr><td class="px-4 py-3 font-mono text-amber-400">403</td><td class="px-4 py-3">VPN / proxy detected</td> <td class="px-4 py-3 text-gray-400">Disable VPN, retry.</td></tr> |
| <tr><td class="px-4 py-3 font-mono text-amber-400">429</td><td class="px-4 py-3">Rate limit exceeded (4 RPM)</td> <td class="px-4 py-3 text-gray-400">Back off & retry; <code class="font-mono">content</code> field shows wait time.</td></tr> |
| <tr><td class="px-4 py-3 font-mono text-red-400">502</td> <td class="px-4 py-3">Upstream provider unreachable</td> <td class="px-4 py-3 text-gray-400">Try again or pick another model.</td></tr> |
| </tbody> |
| </table> |
| </div> |
| </section> |
|
|
| |
| <section id="sdks"> |
| <h2 class="text-2xl font-semibold text-white mb-6">SDK examples</h2> |
|
|
| <div class="grid grid-cols-1 lg:grid-cols-2 gap-4"> |
| <div class="bg-gray-900 border border-gray-800 rounded-xl overflow-hidden"> |
| <div class="px-4 py-2.5 border-b border-gray-800 text-[10px] font-mono uppercase tracking-wider text-gray-500 flex items-center justify-between"> |
| <span>π Python Β· openai</span> |
| <button class="copy-btn text-[10px] font-mono uppercase tracking-wider text-gray-500 hover:text-white transition-colors px-2 py-1 rounded border border-gray-800">Copy</button> |
| </div> |
| <pre class="px-4 py-4"><span class="tk-kw">from</span> openai <span class="tk-kw">import</span> OpenAI |
|
|
| client = <span class="tk-fn">OpenAI</span>( |
| base_url=<span class="tk-str">"<span class="base-url-inline">https://your-apiarium.example.com</span>/v1"</span>, |
| api_key=<span class="tk-str">"api-xxxxxxxxxxxxxxxx"</span>, |
| ) |
|
|
| <span class="tk-cmt"># 1) chat</span> |
| chat = client.chat.completions.<span class="tk-fn">create</span>( |
| model=<span class="tk-str">"gpt-5"</span>, |
| messages=[{<span class="tk-str">"role"</span>: <span class="tk-str">"user"</span>, <span class="tk-str">"content"</span>: <span class="tk-str">"hi"</span>}], |
| ) |
| <span class="tk-fn">print</span>(chat.choices[<span class="tk-num">0</span>].message.content) |
|
|
| <span class="tk-cmt"># 2) image (Gemini 3 or gpt-image)</span> |
| img = client.images.<span class="tk-fn">generate</span>( |
| model=<span class="tk-str">"gemini-3-image"</span>, |
| prompt=<span class="tk-str">"a glass of orange juice on a sunny kitchen counter"</span>, |
| size=<span class="tk-str">"1:1"</span>, |
| ) |
| <span class="tk-fn">print</span>(img.data[<span class="tk-num">0</span>].url) |
|
|
| <span class="tk-cmt"># 3) video (Veo 3) β /v1/videos/generations</span> |
| <span class="tk-kw">import</span> httpx |
| vid = httpx.<span class="tk-fn">post</span>( |
| <span class="tk-str">"<span class="base-url-inline">https://your-apiarium.example.com</span>/v1/videos/generations"</span>, |
| headers={<span class="tk-str">"Authorization"</span>: <span class="tk-str">"Bearer api-xxxxxxxxxxxxxxxx"</span>}, |
| json={<span class="tk-str">"model"</span>: <span class="tk-str">"veo-3"</span>, |
| <span class="tk-str">"prompt"</span>: <span class="tk-str">"a red apple rotating on white"</span>, |
| <span class="tk-str">"duration_seconds"</span>: <span class="tk-str">"8"</span>, |
| <span class="tk-str">"resolution"</span>: <span class="tk-str">"1080p"</span>, |
| <span class="tk-str">"size"</span>: <span class="tk-str">"16:9"</span>}, |
| timeout=<span class="tk-num">600</span>, |
| ).<span class="tk-fn">json</span>() |
| <span class="tk-fn">print</span>(vid[<span class="tk-str">"data"</span>][<span class="tk-num">0</span>][<span class="tk-str">"url"</span>])</pre> |
| </div> |
|
|
| <div class="bg-gray-900 border border-gray-800 rounded-xl overflow-hidden"> |
| <div class="px-4 py-2.5 border-b border-gray-800 text-[10px] font-mono uppercase tracking-wider text-gray-500 flex items-center justify-between"> |
| <span>π¨ JavaScript Β· openai</span> |
| <button class="copy-btn text-[10px] font-mono uppercase tracking-wider text-gray-500 hover:text-white transition-colors px-2 py-1 rounded border border-gray-800">Copy</button> |
| </div> |
| <pre class="px-4 py-4"><span class="tk-kw">import</span> OpenAI <span class="tk-kw">from</span> <span class="tk-str">"openai"</span>; |
|
|
| <span class="tk-kw">const</span> BASE_URL = <span class="tk-str">"<span class="base-url-inline">https://your-apiarium.example.com</span>"</span>; |
| <span class="tk-kw">const</span> API_KEY = <span class="tk-str">"api-xxxxxxxxxxxxxxxx"</span>; |
|
|
| <span class="tk-kw">const</span> client = <span class="tk-kw">new</span> <span class="tk-fn">OpenAI</span>({ |
| baseURL: <span class="tk-str">`${BASE_URL}/v1`</span>, |
| apiKey: API_KEY, |
| }); |
|
|
| <span class="tk-cmt">// 1) image β uses the OpenAI SDK's images.generate</span> |
| <span class="tk-kw">const</span> img = <span class="tk-kw">await</span> client.images.<span class="tk-fn">generate</span>({ |
| model: <span class="tk-str">"gemini-3-image"</span>, |
| prompt: <span class="tk-str">"a glass of orange juice on a sunny kitchen counter"</span>, |
| size: <span class="tk-str">"1:1"</span>, |
| }); |
| <span class="tk-fn">console.log</span>(img.data[<span class="tk-num">0</span>].url); |
|
|
| <span class="tk-cmt">// 2) video β bypass the SDK, hit /v1/videos/generations directly</span> |
| <span class="tk-kw">const</span> vid = <span class="tk-kw">await</span> <span class="tk-fn">fetch</span>(<span class="tk-str">`${BASE_URL}/v1/videos/generations`</span>, { |
| method: <span class="tk-str">"POST"</span>, |
| headers: { |
| <span class="tk-str">"Authorization"</span>: <span class="tk-str">`Bearer ${API_KEY}`</span>, |
| <span class="tk-str">"Content-Type"</span>: <span class="tk-str">"application/json"</span>, |
| }, |
| body: <span class="tk-fn">JSON.stringify</span>({ |
| model: <span class="tk-str">"veo-3"</span>, |
| prompt: <span class="tk-str">"a serene mountain sunset timelapse"</span>, |
| duration_seconds: <span class="tk-str">"8"</span>, |
| resolution: <span class="tk-str">"1080p"</span>, |
| size: <span class="tk-str">"16:9"</span>, |
| }), |
| }).<span class="tk-fn">then</span>(r => r.<span class="tk-fn">json</span>()); |
|
|
| <span class="tk-fn">console.log</span>(vid.data[<span class="tk-num">0</span>].url);</pre> |
| </div> |
| </div> |
| </section> |
|
|
| </article> |
| </div> |
|
|
| </main> |
|
|
| |
| <footer class="border-t border-gray-900 bg-black mt-20 relative z-10"> |
| <div class="max-w-7xl mx-auto px-6 py-8 flex flex-col sm:flex-row justify-between items-center gap-4"> |
| <div class="flex items-center gap-2 text-gray-500 text-sm"> |
| <span class="text-base">π</span> |
| © 2026 APIarium Systems. All rights reserved. |
| </div> |
| <div class="flex items-center gap-4 text-sm text-gray-500"> |
| <a href="/" class="hover:text-gray-300 transition-colors">Home</a> |
| <a href="/openapi-docs" class="hover:text-gray-300 transition-colors">Swagger</a> |
| <a href="/openapi.json" class="hover:text-gray-300 transition-colors">OpenAPI JSON</a> |
| </div> |
| </div> |
| </footer> |
|
|
| <div id="toast" class="fixed bottom-8 left-1/2 -translate-x-1/2 z-50 bg-white text-black px-4 py-3 rounded-lg shadow-2xl font-medium text-sm flex items-center gap-2 hidden"> |
| <svg class="w-5 h-5 text-emerald-600" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg> |
| <span id="toast-text">Copied to clipboard</span> |
| </div> |
|
|
| <script> |
| // ββ Auto-inject the current host into every "base-url" placeholder β |
| const origin = window.location.origin; |
| document.querySelectorAll('#base-url-display, #base-url-1, .base-url-inline').forEach(el => el.textContent = origin); |
| |
| // ββ Copy buttons ββββββββββββββββββββββββββββββββββββββββββββββββββ |
| function flash(msg) { |
| const t = document.getElementById('toast'); |
| const txt = document.getElementById('toast-text'); |
| txt.textContent = msg || 'Copied to clipboard'; |
| t.classList.remove('hidden'); |
| t.classList.add('toast-enter'); |
| setTimeout(() => { t.classList.add('hidden'); t.classList.remove('toast-enter'); }, 1800); |
| } |
| function copy(text, label) { |
| const done = () => flash(label || 'Copied'); |
| if (navigator.clipboard && window.isSecureContext) navigator.clipboard.writeText(text).then(done); |
| else { |
| const ta = document.createElement('textarea'); |
| ta.value = text; ta.style.position = 'fixed'; ta.style.left = '-9999px'; |
| document.body.appendChild(ta); ta.select(); |
| try { document.execCommand('copy'); done(); } catch (e) {} |
| ta.remove(); |
| } |
| } |
| document.getElementById('copy-base-url').addEventListener('click', () => copy(origin, 'Base URL copied')); |
| document.querySelectorAll('.copy-btn').forEach(btn => btn.addEventListener('click', (e) => { |
| const pre = e.target.closest('div').nextElementSibling || e.target.closest('div').parentElement.querySelector('pre'); |
| if (pre) copy(pre.textContent.trim(), 'Snippet copied'); |
| })); |
| |
| // ββ TOC active-link tracking via IntersectionObserver βββββββββββββ |
| const tocLinks = document.querySelectorAll('.toc-link'); |
| const sections = Array.from(tocLinks).map(a => document.querySelector(a.getAttribute('href'))).filter(Boolean); |
| const obs = new IntersectionObserver((entries) => { |
| entries.forEach(e => { |
| if (e.isIntersecting) { |
| tocLinks.forEach(l => l.classList.remove('active')); |
| const active = document.querySelector('.toc-link[href="#' + e.target.id + '"]'); |
| if (active) active.classList.add('active'); |
| } |
| }); |
| }, { rootMargin: '-30% 0px -60% 0px' }); |
| sections.forEach(s => obs.observe(s)); |
| |
| // ββ Models catalog (live from /v1/models) βββββββββββββββββββββββββ |
| const grid = document.getElementById('docs-models-grid'); |
| const badge = document.getElementById('docs-models-badge'); |
| let allModels = []; |
| let currentFilter = 'all'; |
| |
| const TYPE_RULES = [ |
| { key: 'video', test: id => /^veo-/i.test(id) }, |
| { key: 'image', test: id => /image/i.test(id) || /^gpt-image-/i.test(id) }, |
| { key: 'chat', test: () => true }, // catch-all last |
| ]; |
| function typeOf(id) { |
| for (const r of TYPE_RULES) if (r.test(id)) return r.key; |
| return 'chat'; |
| } |
| function badgeClass(t) { |
| if (t === 'video') return 'bg-amber-500/10 text-amber-300 border-amber-500/30'; |
| if (t === 'image') return 'bg-fuchsia-500/10 text-fuchsia-300 border-fuchsia-500/30'; |
| return 'bg-blue-500/10 text-blue-300 border-blue-500/30'; |
| } |
| |
| function renderModels() { |
| const filtered = allModels.filter(m => currentFilter === 'all' || typeOf(m.id) === currentFilter); |
| badge.textContent = `${filtered.length} of ${allModels.length} model${allModels.length === 1 ? '' : 's'}`; |
| if (filtered.length === 0) { |
| grid.innerHTML = `<div class="col-span-full py-10 text-center text-gray-500 text-sm font-mono">No models match this filter.</div>`; |
| return; |
| } |
| grid.innerHTML = filtered.map(m => { |
| const t = typeOf(m.id); |
| return ` |
| <button onclick="copy('${m.id.replace(/'/g, "'")}', 'Model id copied')" |
| class="text-left bg-gray-900/50 border border-gray-800 hover:border-amber-500/40 rounded-xl p-4 transition-colors group"> |
| <div class="flex items-start justify-between gap-2 mb-2"> |
| <code class="font-mono text-sm text-gray-100 group-hover:text-amber-200 truncate">${m.id}</code> |
| <span class="shrink-0 px-2 py-0.5 text-[10px] font-mono border rounded ${badgeClass(t)}">${t}</span> |
| </div> |
| <div class="text-[10px] font-mono uppercase tracking-wider text-gray-500 truncate">${m.owned_by || 'system'}</div> |
| </button>`; |
| }).join(''); |
| } |
| |
| document.querySelectorAll('.model-filter').forEach(btn => { |
| btn.addEventListener('click', (e) => { |
| document.querySelectorAll('.model-filter').forEach(b => b.classList.remove('active-filter', 'border-gray-700', 'text-gray-200')); |
| document.querySelectorAll('.model-filter').forEach(b => b.classList.add('border-gray-800', 'text-gray-400')); |
| e.currentTarget.classList.add('active-filter', 'border-gray-700', 'text-gray-200'); |
| e.currentTarget.classList.remove('border-gray-800', 'text-gray-400'); |
| currentFilter = e.currentTarget.getAttribute('data-filter'); |
| renderModels(); |
| }); |
| }); |
| |
| (async function loadModels() { |
| try { |
| const r = await fetch('/v1/models'); |
| const d = await r.json(); |
| allModels = (d.data || []).sort((a, b) => a.id.localeCompare(b.id)); |
| renderModels(); |
| } catch (e) { |
| grid.innerHTML = `<div class="col-span-full py-10 text-center text-red-400 text-sm font-mono">Failed to load model registry.</div>`; |
| badge.textContent = 'error'; |
| } |
| })(); |
| </script> |
|
|
| </body> |
| </html> |
|
|