"""
Developer Documentation Subdomain Routes  —  developers.<yourdomain>
Serves a beautiful, self-contained API reference page.
Dual-registered on:
  - subdomain 'developers'  (developers.yourdomain.com)
  - /developers/            (main domain fallback for dev mode)
"""
import os

from flask import render_template_string, current_app, request, redirect

_DOCS_HTML = r"""<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>OnlineConvert – Developer Docs</title>
  <link rel="preconnect" href="https://fonts.googleapis.com" />
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
  <style>
    *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
    :root {
      --bg: #0f1117; --surface: #1a1d27; --border: #2a2d3a;
      --text: #e2e8f0; --muted: #8892a4; --accent: #6366f1;
      --accent2: #22d3ee; --green: #22c55e; --orange: #f97316;
      --red: #ef4444; --yellow: #eab308;
      --radius: 10px; --mono: 'JetBrains Mono', monospace;
    }
    body { background: var(--bg); color: var(--text); font-family: 'Inter', sans-serif; font-size: 15px; line-height: 1.7; }
    a { color: var(--accent2); text-decoration: none; }
    a:hover { text-decoration: underline; }
    /* Layout */
    .layout { display: flex; min-height: 100vh; }
    nav { width: 260px; flex-shrink: 0; background: var(--surface); border-right: 1px solid var(--border); padding: 28px 0; position: sticky; top: 0; height: 100vh; overflow-y: auto; }
    nav .logo { padding: 0 24px 24px; border-bottom: 1px solid var(--border); }
    nav .logo h1 { font-size: 17px; font-weight: 700; color: var(--text); }
    nav .logo span { font-size: 12px; color: var(--muted); }
    nav ul { list-style: none; padding: 16px 0; }
    nav ul li a { display: block; padding: 8px 24px; color: var(--muted); font-size: 13px; font-weight: 500; border-left: 2px solid transparent; transition: all .15s; }
    nav ul li a:hover, nav ul li a.active { color: var(--text); border-left-color: var(--accent); background: rgba(99,102,241,.07); text-decoration: none; }
    nav ul li.section-title { padding: 16px 24px 6px; font-size: 10px; font-weight: 700; letter-spacing: .1em; color: var(--muted); text-transform: uppercase; }
    main { flex: 1; padding: 48px 56px; max-width: 900px; }
    @media (max-width: 768px) { .layout { flex-direction: column; } nav { width: 100%; height: auto; position: static; } main { padding: 24px 20px; } }
    /* Sections */
    section { margin-bottom: 64px; scroll-margin-top: 24px; }
    h2 { font-size: 26px; font-weight: 700; margin-bottom: 12px; color: var(--text); }
    h3 { font-size: 18px; font-weight: 600; margin: 32px 0 10px; color: var(--text); }
    p { color: var(--muted); margin-bottom: 14px; }
    /* Endpoint cards */
    .endpoint { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); margin-bottom: 28px; overflow: hidden; }
    .endpoint-header { display: flex; align-items: center; gap: 14px; padding: 16px 20px; border-bottom: 1px solid var(--border); flex-wrap: wrap; }
    .method { font-family: var(--mono); font-size: 12px; font-weight: 700; padding: 4px 10px; border-radius: 6px; letter-spacing: .05em; }
    .method.GET    { background: rgba(34,211,238,.12); color: var(--accent2); }
    .method.POST   { background: rgba(34,197,94,.12);  color: var(--green); }
    .endpoint-path { font-family: var(--mono); font-size: 14px; color: var(--text); }
    .endpoint-desc { margin-left: auto; font-size: 13px; color: var(--muted); }
    .endpoint-body { padding: 20px; }
    /* Code blocks */
    pre { background: #0a0c12; border: 1px solid var(--border); border-radius: 8px; padding: 18px 20px; overflow-x: auto; margin: 12px 0; }
    code { font-family: var(--mono); font-size: 13px; line-height: 1.6; }
    .inline-code { font-family: var(--mono); font-size: 13px; background: rgba(255,255,255,.07); padding: 2px 7px; border-radius: 5px; color: var(--accent2); }
    /* Tables */
    table { width: 100%; border-collapse: collapse; margin: 14px 0; font-size: 13px; }
    th { text-align: left; padding: 10px 14px; background: rgba(255,255,255,.04); color: var(--muted); font-weight: 600; border-bottom: 1px solid var(--border); }
    td { padding: 10px 14px; border-bottom: 1px solid rgba(255,255,255,.04); vertical-align: top; }
    td:first-child { font-family: var(--mono); color: var(--accent2); }
    /* Badges */
    .badge { display: inline-block; font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 99px; }
    .badge.req { background: rgba(239,68,68,.15); color: var(--red); }
    .badge.opt { background: rgba(234,179,8,.12); color: var(--yellow); }
    /* Alert */
    .alert { border-radius: 8px; padding: 14px 18px; margin: 16px 0; font-size: 14px; border-left: 3px solid; }
    .alert.info  { background: rgba(99,102,241,.08); border-color: var(--accent); color: #a5b4fc; }
    .alert.warn  { background: rgba(249,115,22,.08); border-color: var(--orange); color: #fdba74; }
    .alert.success{ background: rgba(34,197,94,.08); border-color: var(--green); color: #86efac; }
    /* Status colors in text */
    .c-green  { color: var(--green); } .c-yellow { color: var(--yellow); }
    .c-blue   { color: var(--accent2); } .c-red { color: var(--red); }
    /* Tab switcher for code samples */
    .tab-group { margin: 14px 0; }
    .tab-buttons { display: flex; gap: 6px; margin-bottom: -1px; }
    .tab-btn { background: none; border: 1px solid var(--border); border-bottom: none; color: var(--muted); font-size: 12px; font-weight: 600; padding: 6px 14px; border-radius: 6px 6px 0 0; cursor: pointer; font-family: var(--mono); }
    .tab-btn.active { background: #0a0c12; color: var(--text); border-color: var(--border); }
    .tab-content { display: none; }
    .tab-content.active { display: block; }
    /* Hero */
    .hero { margin-bottom: 48px; }
    .hero h1 { font-size: 36px; font-weight: 800; background: linear-gradient(135deg, var(--accent) 0%, var(--accent2) 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; margin-bottom: 10px; }
    .hero p { font-size: 17px; color: var(--muted); }
    /* Base URL box */
    .base-url-box { display: flex; align-items: center; gap: 12px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 14px 20px; margin: 20px 0; }
    .base-url-box .label { font-size: 11px; font-weight: 700; color: var(--muted); text-transform: uppercase; letter-spacing: .08em; }
    .base-url-box code { font-family: var(--mono); font-size: 14px; color: var(--accent2); }
  </style>
</head>
<body>
<div class="layout">
  <!-- Sidebar nav -->
  <nav>
    <div class="logo">
      <h1>OnlineConvert API</h1>
      <span>v1 &nbsp;·&nbsp; REST</span>
    </div>
    <ul>
      <li class="section-title">Getting Started</li>
      <li><a href="#introduction">Introduction</a></li>
      <li><a href="#authentication">Authentication</a></li>
      <li><a href="#rate-limits">Rate Limits</a></li>
      <li><a href="#errors">Errors</a></li>
      <li class="section-title">Endpoints</li>
      <li><a href="#ep-root">API Info</a></li>
      <li><a href="#ep-formats">Formats</a></li>
      <li><a href="#ep-upload">Upload</a></li>
      <li><a href="#ep-convert">Convert</a></li>
      <li><a href="#ep-status">Job Status</a></li>
      <li><a href="#ep-download">Download</a></li>
      <li><a href="#ep-keys-me">Key Info</a></li>
      <li class="section-title">Guides</li>
      <li><a href="#guide-quickstart">Quick Start</a></li>
      <li><a href="#guide-webhook">Polling vs Webhooks</a></li>
      <li><a href="#guide-restrictions">Key Restrictions</a></li>
    </ul>
  </nav>

  <!-- Main content -->
  <main>
    <!-- Hero -->
    <div class="hero">
      <h1>Developer Docs</h1>
      <p>Convert files programmatically using the OnlineConvert REST API. Supports images, audio, video, documents, PDFs, archives, and more.</p>
    </div>

    <div class="base-url-box">
      <span class="label">Base URL</span>
      <code>{{ base_url }}</code>
    </div>

    <!-- Introduction -->
    <section id="introduction">
      <h2>Introduction</h2>
      <p>The OnlineConvert API lets you upload files and convert them to any supported format programmatically. All API calls require a valid API key. Responses are always JSON (except file downloads).</p>
      <div class="alert info">All requests must be sent over HTTPS. HTTP connections are rejected.</div>
    </section>

    <!-- Authentication -->
    <section id="authentication">
      <h2>Authentication</h2>
      <p>Pass your API key using <strong>any one</strong> of the following methods:</p>
      <table>
        <thead><tr><th>Method</th><th>Example</th></tr></thead>
        <tbody>
          <tr><td>X-API-Key header</td><td><code>X-API-Key: oc_live_abc123...</code></td></tr>
          <tr><td>Bearer token</td><td><code>Authorization: Bearer oc_live_abc123...</code></td></tr>
          <tr><td>Query parameter</td><td><code>?api_key=oc_live_abc123...</code></td></tr>
        </tbody>
      </table>
      <p>You can create and manage API keys from your <a href="/dashboard/api-keys">account dashboard</a>.</p>
      <div class="alert warn">Keep your API key secret. Do not commit it to source control. Rotate it immediately if it is exposed.</div>

      <h3>Key Restrictions</h3>
      <p>When creating a key you can choose a restriction type:</p>
      <table>
        <thead><tr><th>Type</th><th>Behaviour</th></tr></thead>
        <tbody>
          <tr><td>all</td><td>No restriction – any IP or origin may use the key.</td></tr>
          <tr><td>ip</td><td>Only requests coming from the specified IP addresses are accepted.</td></tr>
          <tr><td>domain</td><td>Only requests with an <span class="inline-code">Origin</span> or <span class="inline-code">Referer</span> matching the allowed domain list are accepted.</td></tr>
        </tbody>
      </table>
    </section>

    <!-- Rate Limits -->
    <section id="rate-limits">
      <h2>Rate Limits</h2>
      <p>API access and rate limits are controlled per subscription plan. Only plans that have API access <strong>enabled</strong> by the admin can use the API. Your admin configures these limits from the plan settings in the admin dashboard.</p>
      <table>
        <thead><tr><th>Plan</th><th>API Access</th><th>Calls / Day</th><th>Calls / Minute</th></tr></thead>
        <tbody>
          <tr><td>Free</td><td class="c-red">Disabled</td><td>—</td><td>—</td></tr>
          <tr><td>Basic</td><td class="c-green">Enabled</td><td>2,000</td><td>60</td></tr>
          <tr><td>Pro</td><td class="c-green">Enabled</td><td>Unlimited</td><td>Unlimited</td></tr>
        </tbody>
      </table>
      <p>When a limit is exceeded the API returns HTTP <span class="inline-code">429 Too Many Requests</span>. If your plan does not have API access, you will receive a <span class="inline-code">403 Forbidden</span> with a clear message and upgrade instructions. Check <span class="inline-code">GET /v1/keys/me</span> at any time to see your remaining quota.</p>
      <div class="alert warn">Rate-limit counters reset every day at <strong>midnight UTC</strong> (daily) and every rolling 60-second window (per-minute).</div>
    </section>

    <!-- Errors -->
    <section id="errors">
      <h2>Errors</h2>
      <p>All errors follow the same structure:</p>
<pre><code>{
  "error": "Human-readable error name",
  "message": "Detailed explanation",
  "hint": "Optional hint to fix the problem"
}</code></pre>
      <table>
        <thead><tr><th>Status</th><th>Meaning</th></tr></thead>
        <tbody>
          <tr><td class="c-blue">200</td><td>OK</td></tr>
          <tr><td class="c-green">202</td><td>Accepted – conversion job queued</td></tr>
          <tr><td class="c-red">400</td><td>Bad Request – missing or invalid parameters</td></tr>
          <tr><td class="c-red">401</td><td>Unauthorized – missing or invalid API key</td></tr>
          <tr><td class="c-red">403</td><td>Forbidden – IP or domain restriction violation</td></tr>
          <tr><td class="c-red">404</td><td>Not Found – job or file does not exist</td></tr>
          <tr><td class="c-yellow">409</td><td>Conflict – job not finished yet</td></tr>
          <tr><td class="c-yellow">429</td><td>Too Many Requests – rate limit reached</td></tr>
          <tr><td class="c-red">500</td><td>Internal Server Error</td></tr>
        </tbody>
      </table>
    </section>

    <!-- Endpoints -->
    <section id="ep-root">
      <h2>API Info</h2>
      <div class="endpoint">
        <div class="endpoint-header">
          <span class="method GET">GET</span>
          <span class="endpoint-path">/v1/</span>
          <span class="endpoint-desc">No auth required</span>
        </div>
        <div class="endpoint-body">
          <p>Returns API version info, available endpoints, and docs URL. Useful for health-checks.</p>
<pre><code>curl {{ base_url }}/</code></pre>
        </div>
      </div>
    </section>

    <section id="ep-formats">
      <h2>List Formats</h2>
      <div class="endpoint">
        <div class="endpoint-header">
          <span class="method GET">GET</span>
          <span class="endpoint-path">/v1/formats</span>
        </div>
        <div class="endpoint-body">
          <p>Returns all supported input and output formats grouped by converter type.</p>
<pre><code>curl {{ base_url }}/formats \
  -H "X-API-Key: YOUR_API_KEY"</code></pre>
          <h3>Response</h3>
<pre><code>{
  "ok": true,
  "filetypes": {
    "image": {
      "input_formats": ["jpg", "png", "webp", "gif", "bmp", "tiff"],
      "output_formats": ["jpg", "png", "webp", "gif", "bmp", "tiff", "ico"]
    },
    "audio": { "input_formats": [...], "output_formats": [...] },
    ...
  }
}</code></pre>
        </div>
      </div>
    </section>

    <section id="ep-upload">
      <h2>Upload a File</h2>
      <div class="endpoint">
        <div class="endpoint-header">
          <span class="method POST">POST</span>
          <span class="endpoint-path">/v1/upload</span>
        </div>
        <div class="endpoint-body">
          <p>Upload one or more files. Returns a <span class="inline-code">file_path</span> you can pass directly to <span class="inline-code">/v1/convert</span>.</p>
          <h3>Request (multipart/form-data)</h3>
          <table>
            <thead><tr><th>Field</th><th>Type</th><th></th><th>Description</th></tr></thead>
            <tbody>
              <tr><td>file</td><td>File</td><td><span class="badge req">required</span></td><td>File to upload (repeat for multiple files)</td></tr>
            </tbody>
          </table>

          <div class="tab-group">
            <div class="tab-buttons">
              <button class="tab-btn active" onclick="switchTab(this,'upload','curl')">cURL</button>
              <button class="tab-btn" onclick="switchTab(this,'upload','python')">Python</button>
              <button class="tab-btn" onclick="switchTab(this,'upload','js')">JavaScript</button>
            </div>
            <div id="upload-curl" class="tab-content active">
<pre><code>curl -X POST {{ base_url }}/upload \
  -H "X-API-Key: YOUR_API_KEY" \
  -F "file=@/path/to/image.png"</code></pre>
            </div>
            <div id="upload-python" class="tab-content">
<pre><code>import requests

resp = requests.post(
    "{{ base_url }}/upload",
    headers={"X-API-Key": "YOUR_API_KEY"},
    files={"file": open("image.png", "rb")},
)
data = resp.json()
file_path = data["files"][0]["file_path"]</code></pre>
            </div>
            <div id="upload-js" class="tab-content">
<pre><code>const form = new FormData();
form.append("file", fileInput.files[0]);

const resp = await fetch("{{ base_url }}/upload", {
  method: "POST",
  headers: { "X-API-Key": "YOUR_API_KEY" },
  body: form,
});
const { files } = await resp.json();
const filePath = files[0].file_path;</code></pre>
            </div>
          </div>

          <h3>Response</h3>
<pre><code>{
  "ok": true,
  "files": [
    {
      "file_path": "a3f7bc91d8e2/image.png",
      "file_name": "image.png",
      "file_size": 204800,
      "upload_id": 42,
      "extension": "png"
    }
  ]
}</code></pre>
        </div>
      </div>
    </section>

    <section id="ep-convert">
      <h2>Convert a File</h2>
      <div class="endpoint">
        <div class="endpoint-header">
          <span class="method POST">POST</span>
          <span class="endpoint-path">/v1/convert</span>
        </div>
        <div class="endpoint-body">
          <p>Upload a file and start a conversion job in one request. Returns a <span class="inline-code">job_id</span>; poll <span class="inline-code">/v1/status/{job_id}</span> to check progress.</p>
          <h3>Request (multipart/form-data)</h3>
          <table>
            <thead><tr><th>Field</th><th>Type</th><th></th><th>Description</th></tr></thead>
            <tbody>
              <tr><td>file</td><td>File</td><td><span class="badge req">required*</span></td><td>File to convert</td></tr>
              <tr><td>file_path</td><td>string</td><td><span class="badge opt">optional*</span></td><td>Path from a prior <code>/upload</code> call (alternative to sending <code>file</code>)</td></tr>
              <tr><td>filetype</td><td>string</td><td><span class="badge req">required</span></td><td>Converter category: <code>image</code>, <code>audio</code>, <code>video</code>, <code>document</code>, <code>pdf</code>, <code>hash</code>, <code>archive</code>, <code>ebook</code></td></tr>
              <tr><td>output_format</td><td>string</td><td><span class="badge req">required</span></td><td>Target extension, e.g. <code>jpg</code>, <code>mp3</code>, <code>pdf</code></td></tr>
              <tr><td>options</td><td>JSON string</td><td><span class="badge opt">optional</span></td><td>Converter-specific options (quality, resize, etc.)</td></tr>
            </tbody>
          </table>
          <p>* Either <code>file</code> or <code>file_path</code> is required.</p>

          <div class="tab-group">
            <div class="tab-buttons">
              <button class="tab-btn active" onclick="switchTab(this,'convert','curl')">cURL</button>
              <button class="tab-btn" onclick="switchTab(this,'convert','python')">Python</button>
              <button class="tab-btn" onclick="switchTab(this,'convert','js')">JavaScript</button>
            </div>
            <div id="convert-curl" class="tab-content active">
<pre><code>curl -X POST {{ base_url }}/convert \
  -H "X-API-Key: YOUR_API_KEY" \
  -F "file=@/path/to/input.png" \
  -F "filetype=image" \
  -F "output_format=webp"</code></pre>
            </div>
            <div id="convert-python" class="tab-content">
<pre><code>import requests, time

API_KEY = "YOUR_API_KEY"
BASE    = "{{ base_url }}"

# 1. Start conversion
resp = requests.post(f"{BASE}/convert",
    headers={"X-API-Key": API_KEY},
    files={"file": open("input.png", "rb")},
    data={"filetype": "image", "output_format": "webp"},
)
job = resp.json()
job_id = job["job_id"]

# 2. Poll until done
while True:
    status = requests.get(f"{BASE}/status/{job_id}",
        headers={"X-API-Key": API_KEY}).json()
    if status["status"] == "finished":
        break
    if status["status"] == "failed":
        raise Exception(status.get("error"))
    time.sleep(2)

# 3. Download result
out = requests.get(f"{BASE}/download/{job_id}",
    headers={"X-API-Key": API_KEY})
with open("output.webp", "wb") as f:
    f.write(out.content)</code></pre>
            </div>
            <div id="convert-js" class="tab-content">
<pre><code>const API_KEY = "YOUR_API_KEY";
const BASE    = "{{ base_url }}";

// 1. Start conversion
const form = new FormData();
form.append("file", fileInput.files[0]);
form.append("filetype", "image");
form.append("output_format", "webp");

const { job_id } = await fetch(`${BASE}/convert`, {
  method: "POST",
  headers: { "X-API-Key": API_KEY },
  body: form,
}).then(r => r.json());

// 2. Poll until done
let status;
do {
  await new Promise(r => setTimeout(r, 2000));
  status = await fetch(`${BASE}/status/${job_id}`,
    { headers: { "X-API-Key": API_KEY } }).then(r => r.json());
} while (status.status === "pending" || status.status === "processing");

// 3. Download result
if (status.status === "finished") {
  window.location.href = status.download_url;
}</code></pre>
            </div>
          </div>

          <h3>Response (202 Accepted)</h3>
<pre><code>{
  "ok": true,
  "job_id": "a1b2c3d4-e5f6-...",
  "status": "pending",
  "status_url": "{{ base_url }}/status/a1b2c3d4-e5f6-...",
  "download_url": "{{ base_url }}/download/a1b2c3d4-e5f6-..."
}</code></pre>
        </div>
      </div>
    </section>

    <section id="ep-status">
      <h2>Job Status</h2>
      <div class="endpoint">
        <div class="endpoint-header">
          <span class="method GET">GET</span>
          <span class="endpoint-path">/v1/status/{job_id}</span>
        </div>
        <div class="endpoint-body">
          <p>Poll the status of a conversion job.</p>
<pre><code>curl {{ base_url }}/status/JOB_ID \
  -H "X-API-Key: YOUR_API_KEY"</code></pre>
          <h3>Response</h3>
<pre><code>// Pending / Processing
{ "ok": true, "job_id": "...", "status": "pending" }

// Finished
{ "ok": true, "job_id": "...", "status": "finished",
  "download_url": "{{ base_url }}/download/..." }

// Failed
{ "ok": false, "job_id": "...", "status": "failed",
  "error": "Unsupported codec" }</code></pre>
          <table>
            <thead><tr><th>Status</th><th>Meaning</th></tr></thead>
            <tbody>
              <tr><td class="c-yellow">pending</td><td>Job is queued, not yet started</td></tr>
              <tr><td class="c-blue">processing</td><td>Conversion is running</td></tr>
              <tr><td class="c-green">finished</td><td>Done — download is ready</td></tr>
              <tr><td class="c-red">failed</td><td>Conversion failed — see <code>error</code> field</td></tr>
            </tbody>
          </table>
        </div>
      </div>
    </section>

    <section id="ep-download">
      <h2>Download Result</h2>
      <div class="endpoint">
        <div class="endpoint-header">
          <span class="method GET">GET</span>
          <span class="endpoint-path">/v1/download/{job_id}</span>
        </div>
        <div class="endpoint-body">
          <p>Download the converted file. Returns the raw file bytes with <span class="inline-code">Content-Disposition: attachment</span>. Only works once the job status is <span class="c-green">finished</span>.</p>
<pre><code>curl -O -J {{ base_url }}/download/JOB_ID \
  -H "X-API-Key: YOUR_API_KEY"</code></pre>
          <div class="alert warn">Returns <strong>409 Conflict</strong> if the job is not finished yet. Poll <code>/v1/status</code> first.</div>
        </div>
      </div>
    </section>

    <section id="ep-keys-me">
      <h2>Key Info &amp; Quota</h2>
      <div class="endpoint">
        <div class="endpoint-header">
          <span class="method GET">GET</span>
          <span class="endpoint-path">/v1/keys/me</span>
        </div>
        <div class="endpoint-body">
          <p>Inspect the current API key and check your remaining rate-limit quota.</p>
<pre><code>curl {{ base_url }}/keys/me \
  -H "X-API-Key: YOUR_API_KEY"</code></pre>
          <h3>Response</h3>
<pre><code>{
  "ok": true,
  "key_id": 3,
  "name": "My Production Key",
  "restriction_type": "ip",
  "allowed_ips": ["203.0.113.5"],
  "allowed_domains": [],
  "usage_count": 1042,
  "last_used_at": "2026-03-27T10:14:55",
  "created_at": "2026-01-01T00:00:00",
  "rate_limits": {
    "calls_per_day": 2000,
    "calls_per_minute": 60,
    "used_today": 412,
    "used_this_minute": 3,
    "remaining_today": 1588
  }
}</code></pre>
        </div>
      </div>
    </section>

    <!-- Guides -->
    <section id="guide-quickstart">
      <h2>Quick Start</h2>
      <p>Get a file converted in under a minute:</p>
      <ol style="padding-left:20px; color: var(--muted);">
        <li style="margin-bottom:8px">Log in and go to <a href="/dashboard/api-keys">API Keys</a> in your dashboard.</li>
        <li style="margin-bottom:8px">Click <strong>Create Key</strong>. Copy the key — it won't be shown again.</li>
        <li style="margin-bottom:8px">Call <span class="inline-code">POST /v1/convert</span> with your file and desired output format.</li>
        <li style="margin-bottom:8px">Poll <span class="inline-code">GET /v1/status/{job_id}</span> until status is <span class="c-green">finished</span>.</li>
        <li>Download from <span class="inline-code">GET /v1/download/{job_id}</span>.</li>
      </ol>
      <div class="alert success">That's it! Check the endpoint examples above for code you can copy-paste.</div>
    </section>

    <section id="guide-webhook">
      <h2>Polling vs Webhooks</h2>
      <p>Currently, the API uses a <strong>polling</strong> model. After submitting a conversion job, poll <span class="inline-code">/v1/status/{job_id}</span> every 2–5 seconds until the status changes to <span class="c-green">finished</span> or <span class="c-red">failed</span>.</p>
      <p>Recommended polling interval: <strong>2 seconds</strong> for short jobs, <strong>5–10 seconds</strong> for large video/document jobs.</p>
      <div class="alert info">Webhook support is on our roadmap. Watch your dashboard for announcements.</div>
    </section>

    <section id="guide-restrictions">
      <h2>Key Restrictions</h2>
      <p>You can lock each API key to specific IPs or domains to prevent misuse if the key is ever exposed.</p>

      <h3>IP Restriction</h3>
      <p>Set <span class="inline-code">restriction_type = ip</span> and provide one or more IPv4/IPv6 addresses. Only requests originating from those IPs will be accepted.</p>
      <div class="alert warn">If you are behind a NAT or proxy, use the public egress IP of your server, not a private IP.</div>

      <h3>Domain Restriction</h3>
      <p>Set <span class="inline-code">restriction_type = domain</span> and list your domains (e.g. <span class="inline-code">example.com</span>). The API checks the <span class="inline-code">Origin</span> and <span class="inline-code">Referer</span> headers — ideal for browser-based integrations.</p>
      <div class="alert info">Wildcard subdomains are supported: <span class="inline-code">*.example.com</span> allows all subdomains of example.com.</div>
    </section>

  </main>
</div>

<script>
// Tab switcher
function switchTab(btn, group, tab) {
  btn.closest('.tab-group').querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
  btn.classList.add('active');
  btn.closest('.tab-group').querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
  document.getElementById(group + '-' + tab).classList.add('active');
}

// Highlight active nav link on scroll
const sections = document.querySelectorAll('section[id]');
const navLinks  = document.querySelectorAll('nav a[href^="#"]');
function updateNav() {
  let current = '';
  sections.forEach(s => { if (window.scrollY >= s.offsetTop - 80) current = s.id; });
  navLinks.forEach(a => {
    a.classList.toggle('active', a.getAttribute('href') === '#' + current);
  });
}
window.addEventListener('scroll', updateNav, { passive: true });
updateNav();
</script>
</body>
</html>"""


def register_developers_routes(app):

    def _base_url():
        server_name = app.config.get("SERVER_NAME") or request.host.split(":")[0]
        if os.environ.get("REPLIT_DEV_DOMAIN"):
            return f"https://{request.host}/api/v1"
        return f"https://api.{server_name}/v1"

    def _subdomains_enabled():
        """Return True when subdomain routing is on and we're not in Replit dev."""
        if os.environ.get("REPLIT_DEV_DOMAIN"):
            return False
        try:
            from helpers import get_site_settings
            return get_site_settings().get("subdomains_enabled", "0") == "1"
        except Exception:
            return False

    def _render_docs():
        # When subdomain routing is enabled, the canonical URL is
        # developers.<SERVER_NAME>/ — redirect the main-domain path there.
        # But only if we are NOT already on the developers subdomain
        # (otherwise we'd cause an infinite redirect loop).
        if _subdomains_enabled():
            server_name = app.config.get("SERVER_NAME") or request.host.split(":")[0]
            current_host = request.host.split(":")[0].lower()
            if current_host != f"developers.{server_name}".lower():
                return redirect(f"https://developers.{server_name}/", code=301)
        return render_template_string(_DOCS_HTML, base_url=_base_url())

    # ── Main domain fallback  /developers/ ───────────────────────────────────
    app.add_url_rule("/developers/", endpoint="developers_docs_index",
                     view_func=_render_docs)
    app.add_url_rule("/developers",  endpoint="developers_docs_root",
                     view_func=_render_docs)

    # ── Subdomain  developers.<SERVER_NAME> ───────────────────────────────────
    if not os.environ.get("REPLIT_DEV_DOMAIN"):
        try:
            app.add_url_rule("/", endpoint="dev_docs_sub",
                             view_func=_render_docs, subdomain="developers")
        except Exception:
            pass
