Files
Zordon/docs/api.html
T
brett f0be78adcc REST API endpoint streamlining and cleanup (#133)
* Consolidate engine_info api calls

* Change api methods to use POST when possible

* Delete engine API cleanup

* Remove redundant installed_engines endpoint

* Update proxy to use similar named methods to new API calls

* More API cleanup

* Jobs API cleanup

* More jobs API cleanup

* Fix add jobs error due to null queue

* Remove unnecessary full_status and snapshot API endpoints

* Streamline add job POST endpoint

* Fix test after method name change
2026-06-06 12:04:52 -05:00

578 lines
23 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Zordon API Reference</title>
<style>
:root {
--bg: #0d1117;
--surface: #161b22;
--surface2: #1c2333;
--border: #30363d;
--text: #e6edf3;
--text-muted: #8b949e;
--blue: #58a6ff;
--green: #3fb950;
--orange: #d29922;
--red: #f85149;
--purple: #bc8cff;
--cyan: #39d2c0;
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.6;
}
header {
border-bottom: 1px solid var(--border);
padding: 56px 24px 36px;
text-align: center;
}
header h1 {
margin: 0 0 10px;
font-size: 2.7rem;
font-weight: 800;
color: var(--text);
}
header p {
max-width: 760px;
margin: 0 auto;
color: var(--text-muted);
font-size: 1.05rem;
}
main {
display: grid;
grid-template-columns: 250px minmax(0, 1fr);
gap: 32px;
max-width: 1180px;
margin: 0 auto;
padding: 36px 24px 56px;
}
nav {
position: sticky;
top: 18px;
align-self: start;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 8px;
padding: 16px;
}
nav strong {
display: block;
margin-bottom: 8px;
color: var(--text);
}
nav a {
display: block;
padding: 5px 0;
color: var(--text-muted);
text-decoration: none;
font-size: .92rem;
}
nav a:hover { color: var(--blue); }
section { margin-bottom: 44px; }
h2 {
display: inline-block;
margin: 0 0 18px;
padding-bottom: 8px;
border-bottom: 2px solid var(--blue);
font-size: 1.45rem;
}
h3 {
margin: 0 0 10px;
color: var(--cyan);
font-size: 1.08rem;
}
p, li { color: var(--text-muted); }
code {
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
background: var(--surface2);
border-radius: 4px;
padding: 2px 5px;
color: var(--cyan);
font-size: .9em;
}
pre {
margin: 12px 0;
padding: 14px;
overflow-x: auto;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text);
}
pre code {
padding: 0;
background: transparent;
color: inherit;
}
table {
width: 100%;
margin: 12px 0;
border-collapse: collapse;
font-size: .92rem;
}
th, td {
padding: 9px 10px;
border: 1px solid var(--border);
text-align: left;
vertical-align: top;
}
th {
background: var(--surface2);
color: var(--text);
font-weight: 600;
}
td { color: var(--text-muted); }
.meta-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 14px;
margin-bottom: 24px;
}
.panel, .endpoint {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 8px;
padding: 18px;
}
.panel h3 { color: var(--text); }
.panel ul { margin: 8px 0 0; padding-left: 20px; }
.endpoint {
margin-bottom: 16px;
}
.endpoint-header {
display: flex;
align-items: baseline;
gap: 10px;
flex-wrap: wrap;
margin-bottom: 8px;
}
.method {
display: inline-block;
min-width: 52px;
padding: 2px 8px;
border-radius: 4px;
text-align: center;
font-weight: 700;
font-size: .76rem;
letter-spacing: .02em;
}
.get { background: rgba(88, 166, 255, .16); color: var(--blue); border: 1px solid rgba(88, 166, 255, .35); }
.post { background: rgba(63, 185, 80, .16); color: var(--green); border: 1px solid rgba(63, 185, 80, .35); }
.multi { background: rgba(210, 153, 34, .16); color: var(--orange); border: 1px solid rgba(210, 153, 34, .35); }
.path {
color: var(--text);
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
font-weight: 650;
overflow-wrap: anywhere;
}
.note {
margin-top: 12px;
padding: 10px 12px;
border-left: 3px solid var(--orange);
background: rgba(210, 153, 34, .08);
color: var(--text-muted);
}
.danger {
border-left-color: var(--red);
background: rgba(248, 81, 73, .08);
}
.callers {
margin: 10px 0 0;
padding-left: 20px;
}
.callers li { margin: 2px 0; }
@media (max-width: 820px) {
main { grid-template-columns: 1fr; }
nav { position: static; }
header h1 { font-size: 2.1rem; }
}
</style>
</head>
<body>
<header>
<h1>Zordon API Reference</h1>
<p>Flask endpoints exposed by <code>src/api/api_server.py</code>, with the in-repo client wrapper in <code>src/api/server_proxy.py</code>.</p>
</header>
<main>
<nav aria-label="API sections">
<strong>Sections</strong>
<a href="#overview">Overview</a>
<a href="#jobs">Jobs</a>
<a href="#job-lifecycle">Job Lifecycle</a>
<a href="#status">Status and Environment</a>
<a href="#engines">Engines</a>
<a href="#debug">Debug</a>
<a href="#cleanup">Cleanup Review</a>
</nav>
<div>
<section id="overview">
<h2>Overview</h2>
<div class="meta-grid">
<div class="panel">
<h3>Versioning</h3>
<ul>
<li>Current API version: <code>0.1</code></li>
<li><code>RenderServerProxy.request()</code> sends <code>X-API-Version</code>.</li>
<li>The server does not currently validate that header.</li>
</ul>
</div>
<div class="panel">
<h3>Response Conventions</h3>
<ul>
<li>JSON endpoints return Flask-serialized dictionaries or lists.</li>
<li>File endpoints return <code>send_file()</code> responses.</li>
<li>Most errors are plain text with <code>400</code>, <code>404</code>, <code>500</code>, or <code>503</code>.</li>
<li><code>JobNotFoundError</code> maps to HTTP <code>400</code>.</li>
</ul>
</div>
</div>
</section>
<section id="jobs">
<h2>Jobs</h2>
<article class="endpoint">
<div class="endpoint-header"><span class="method get">GET</span><span class="path">/api/jobs</span></div>
<p>Returns all render jobs and a cache token.</p>
<pre><code>{
"jobs": [{"id": "job-id", "name": "job name", "status": "running"}],
"token": "cache-token"
}</code></pre>
<p>Known callers:</p>
<ul class="callers">
<li><code>RenderServerProxy.get_jobs()</code></li>
<li><code>src/ui/main_window.py</code></li>
</ul>
</article>
<article class="endpoint">
<div class="endpoint-header"><span class="method get">GET</span><span class="path">/api/jobs/long_poll</span></div>
<p>Long-polls the job list until the supplied cache token changes or 30 seconds elapse.</p>
<table>
<tr><th>Query Parameter</th><th>Required</th><th>Description</th></tr>
<tr><td><code>token</code></td><td>No</td><td>Cache token returned by <code>/api/jobs</code>.</td></tr>
</table>
<p>Returns <code>200</code> with job data when changed, or <code>204</code> when no change arrives before timeout.</p>
</article>
<article class="endpoint">
<div class="endpoint-header"><span class="method get">GET</span><span class="path">/api/jobs/status/&lt;status_val&gt;</span></div>
<p>Returns jobs matching a render status converted by <code>string_to_status()</code>.</p>
<div class="note">No <code>RenderServerProxy</code> wrapper or in-repo caller was found.</div>
</article>
<article class="endpoint">
<div class="endpoint-header"><span class="method get">GET</span><span class="path">/api/jobs/&lt;job_id&gt;</span></div>
<p>Returns one job as JSON.</p>
<p>Known callers include <code>RenderServerProxy.get_job()</code>, <code>add_job.py</code>, <code>src/ui/main_window.py</code>, <code>src/distributed_job_manager.py</code>, and <code>tests/job_creation_tests.py</code>.</p>
</article>
<article class="endpoint">
<div class="endpoint-header"><span class="method get">GET</span><span class="path">/api/jobs/&lt;job_id&gt;/logs</span></div>
<p>Returns the job log file as <code>text/plain</code>. The main window opens this URL directly.</p>
</article>
<article class="endpoint">
<div class="endpoint-header"><span class="method get">GET</span><span class="path">/api/jobs/&lt;job_id&gt;/files</span></div>
<p>Returns a list of output filenames for the job.</p>
<p>Known callers: <code>RenderServerProxy.get_job_files()</code> and <code>src/utilities/server_helper.py</code>.</p>
</article>
<article class="endpoint">
<div class="endpoint-header"><span class="method get">GET</span><span class="path">/api/jobs/&lt;job_id&gt;/download</span></div>
<p>Downloads one output file.</p>
<table>
<tr><th>Query Parameter</th><th>Required</th><th>Description</th></tr>
<tr><td><code>filename</code></td><td>Yes</td><td>Case-insensitive filename from the job file list.</td></tr>
</table>
<p>Returns <code>200</code> with an attachment, <code>400</code> when <code>filename</code> is missing, or <code>404</code> when the file is not found.</p>
</article>
<article class="endpoint">
<div class="endpoint-header"><span class="method get">GET</span><span class="path">/api/jobs/&lt;job_id&gt;/download_all</span></div>
<p>Creates a temporary zip of the job output directory and downloads it.</p>
<p>Known callers: <code>RenderServerProxy.download_all_job_files()</code>, <code>src/ui/main_window.py</code>, and <code>src/utilities/server_helper.py</code>.</p>
</article>
<article class="endpoint">
<div class="endpoint-header"><span class="method get">GET</span><span class="path">/api/jobs/&lt;job_id&gt;/thumbnail</span></div>
<p>Returns a generated preview image or video for a job.</p>
<table>
<tr><th>Query Parameter</th><th>Required</th><th>Description</th></tr>
<tr><td><code>size</code></td><td>No</td><td><code>big</code> is parsed but not currently applied.</td></tr>
<tr><td><code>video_ok</code></td><td>No</td><td>If truthy and a video preview exists, video can be returned.</td></tr>
</table>
<div class="note">Cleanup note: <code>size=big</code> is parsed into <code>big_thumb</code> but not used.</div>
</article>
</section>
<section id="job-lifecycle">
<h2>Job Lifecycle</h2>
<article class="endpoint">
<div class="endpoint-header"><span class="method post">POST</span><span class="path">/api/jobs</span></div>
<p>Adds one or more render jobs. Accepts either a JSON request body or multipart form data with a <code>json</code> field and optional <code>file</code> upload.</p>
<table>
<tr><th>Common Field</th><th>Description</th></tr>
<tr><td><code>name</code></td><td>Display name for the render job.</td></tr>
<tr><td><code>renderer</code></td><td>Render engine name such as <code>blender</code> or <code>ffmpeg</code>.</td></tr>
<tr><td><code>start_frame</code></td><td>First frame to render.</td></tr>
<tr><td><code>end_frame</code></td><td>Last frame to render.</td></tr>
<tr><td><code>args</code></td><td>Engine-specific render arguments.</td></tr>
<tr><td><code>enable_split_jobs</code></td><td>Whether distributed subjobs may be created.</td></tr>
<tr><td><code>child_jobs</code></td><td>Optional subjob definitions.</td></tr>
<tr><td><code>local_path</code></td><td>Local file path used when posting to localhost.</td></tr>
</table>
<p>Known callers include <code>RenderServerProxy.create_job()</code>, <code>add_job.py</code>, <code>src/ui/add_job_window.py</code>, <code>src/distributed_job_manager.py</code>, and integration tests.</p>
</article>
<article class="endpoint">
<div class="endpoint-header"><span class="method post">POST</span><span class="path">/api/jobs/&lt;job_id&gt;/cancel</span></div>
<p>Cancels a job. Requires a truthy <code>confirm</code> query parameter.</p>
<table>
<tr><th>Query Parameter</th><th>Required</th><th>Description</th></tr>
<tr><td><code>confirm</code></td><td>Yes</td><td>Must be truthy or the request is rejected.</td></tr>
<tr><td><code>redirect</code></td><td>No</td><td>If truthy, redirects to <code>index</code>.</td></tr>
</table>
</article>
<article class="endpoint">
<div class="endpoint-header"><span class="method post">POST</span><span class="path">/api/jobs/&lt;job_id&gt;/delete</span></div>
<p>Deletes a job, stops it first, deletes previews, and removes owned upload/output directories when safe.</p>
<table>
<tr><th>Query Parameter</th><th>Required</th><th>Description</th></tr>
<tr><td><code>confirm</code></td><td>Yes</td><td>Must be truthy or the request is rejected.</td></tr>
</table>
</article>
<article class="endpoint">
<div class="endpoint-header"><span class="method post">POST</span><span class="path">/api/jobs/&lt;job_id&gt;/subjob_update</span></div>
<p>Notifies a parent job that a child/subjob changed state. The request body is the JSON representation of the subjob.</p>
</article>
</section>
<section id="status">
<h2>Status and Environment</h2>
<article class="endpoint">
<div class="endpoint-header"><span class="method get">GET</span><span class="path">/api/heartbeat</span></div>
<p>Returns the current timestamp as plain text. Used by <code>RenderServerProxy.check_connection()</code>.</p>
</article>
<article class="endpoint">
<div class="endpoint-header"><span class="method get">GET</span><span class="path">/api/status</span></div>
<p>Returns local system and queue status, including operating system, CPU, memory, job counts, hostname, port, app version, and API version.</p>
</article>
<article class="endpoint">
<div class="endpoint-header"><span class="method get">GET</span><span class="path">/api/presets</span></div>
<p>Returns <code>config/presets.yaml</code>.</p>
<div class="note">No in-repo callers were found.</div>
</article>
<article class="endpoint">
<div class="endpoint-header"><span class="method get">GET</span><span class="path">/api/cpu_benchmark</span></div>
<p>Runs a CPU benchmark for 10 seconds and returns the score as plain text. Used by <code>src/utilities/server_helper.py</code>.</p>
</article>
<article class="endpoint">
<div class="endpoint-header"><span class="method get">GET</span><span class="path">/api/disk_benchmark</span></div>
<p>Runs a disk I/O benchmark and returns write/read speeds.</p>
<div class="note">No in-repo callers were found.</div>
</article>
</section>
<section id="engines">
<h2>Engines</h2>
<article class="endpoint">
<div class="endpoint-header"><span class="method get">GET</span><span class="path">/api/engines/for_filename</span></div>
<p>Returns the engine name suitable for a project filename.</p>
<table>
<tr><th>Query Parameter</th><th>Required</th><th>Description</th></tr>
<tr><td><code>filename</code></td><td>Yes</td><td>Project filename or path. The client currently sends only the basename.</td></tr>
</table>
</article>
<article class="endpoint">
<div class="endpoint-header"><span class="method get">GET</span><span class="path">/api/engines</span></div>
<p>Returns installed engine data keyed by engine name.</p>
<table>
<tr><th>Query Parameter</th><th>Required</th><th>Description</th></tr>
<tr><td><code>response_type</code></td><td>No</td><td><code>standard</code> or <code>full</code>; defaults to <code>standard</code>.</td></tr>
</table>
<p><code>full</code> responses include supported extensions, supported export formats, system info, and UI options.</p>
<p>Known callers: <code>RenderServerProxy.get_engines()</code>, <code>src/ui/settings_window.py</code>, and <code>src/ui/engine_browser.py</code>.</p>
</article>
<article class="endpoint">
<div class="endpoint-header"><span class="method get">GET</span><span class="path">/api/engines/names</span></div>
<p>Returns installed engine names as a list without instantiating engine classes. Use this for lightweight selection UIs that only need engine names.</p>
<p>Known callers: <code>RenderServerProxy.get_engine_names()</code> and <code>src/ui/add_job_window.py</code>.</p>
</article>
<article class="endpoint">
<div class="endpoint-header"><span class="method get">GET</span><span class="path">/api/engines/&lt;engine_name&gt;</span></div>
<p>Returns installed version data for a single engine.</p>
<table>
<tr><th>Query Parameter</th><th>Required</th><th>Description</th></tr>
<tr><td><code>response_type</code></td><td>No</td><td><code>standard</code> or <code>full</code>; defaults to <code>standard</code>.</td></tr>
</table>
<p><code>full</code> responses include supported extensions, supported export formats, system info, and UI options.</p>
<p>Known caller: <code>RenderServerProxy.get_engine()</code> in the add-job window.</p>
</article>
<article class="endpoint">
<div class="endpoint-header"><span class="method get">GET</span><span class="path">/api/engines/&lt;engine_name&gt;/availability</span></div>
<p>Returns whether an engine can accept jobs on this server, plus CPU count, installed versions, and hostname.</p>
</article>
<article class="endpoint">
<div class="endpoint-header"><span class="method get">GET</span><span class="path">/api/engines/&lt;engine_name&gt;/args</span></div>
<p>Returns engine arguments.</p>
<div class="note">No in-repo callers were found.</div>
</article>
<article class="endpoint">
<div class="endpoint-header"><span class="method get">GET</span><span class="path">/api/engines/&lt;engine_name&gt;/help</span></div>
<p>Returns engine help text. The add-job window opens this URL directly.</p>
</article>
<article class="endpoint">
<div class="endpoint-header"><span class="method get">GET</span><span class="path">/api/engines/download_available</span></div>
<p>Checks whether a managed engine version is available to download.</p>
<table>
<tr><th>Query Parameter</th><th>Required</th><th>Description</th></tr>
<tr><td><code>engine</code></td><td>Yes</td><td>Engine name.</td></tr>
<tr><td><code>version</code></td><td>Yes</td><td>Engine version.</td></tr>
<tr><td><code>system_os</code></td><td>No</td><td>Target OS.</td></tr>
<tr><td><code>cpu</code></td><td>No</td><td>Target CPU architecture.</td></tr>
</table>
<div class="note">No in-repo callers were found.</div>
</article>
<article class="endpoint">
<div class="endpoint-header"><span class="method get">GET</span><span class="path">/api/engines/most_recent_version</span></div>
<p>Finds the most recent downloadable version for an engine.</p>
<table>
<tr><th>Query Parameter</th><th>Required</th><th>Description</th></tr>
<tr><td><code>engine</code></td><td>Yes</td><td>Engine name.</td></tr>
<tr><td><code>system_os</code></td><td>No</td><td>Target OS.</td></tr>
<tr><td><code>cpu</code></td><td>No</td><td>Target CPU architecture.</td></tr>
</table>
<div class="note">No in-repo callers were found.</div>
</article>
<article class="endpoint">
<div class="endpoint-header"><span class="method post">POST</span><span class="path">/api/engines/download</span></div>
<p>Downloads a managed engine version.</p>
<table>
<tr><th>Query Parameter</th><th>Required</th><th>Description</th></tr>
<tr><td><code>engine</code></td><td>Yes</td><td>Engine name.</td></tr>
<tr><td><code>version</code></td><td>Yes</td><td>Engine version.</td></tr>
<tr><td><code>system_os</code></td><td>No</td><td>Target OS.</td></tr>
<tr><td><code>cpu</code></td><td>No</td><td>Target CPU architecture.</td></tr>
</table>
<div class="note">Settings currently calls <code>EngineManager.download_engine()</code> directly instead of this API route.</div>
</article>
<article class="endpoint">
<div class="endpoint-header"><span class="method post">POST</span><span class="path">/api/engines/delete</span></div>
<p>Deletes a managed engine download.</p>
<table>
<tr><th>JSON Field</th><th>Required</th><th>Description</th></tr>
<tr><td><code>engine</code></td><td>Yes</td><td>Engine name.</td></tr>
<tr><td><code>version</code></td><td>Yes</td><td>Engine version.</td></tr>
<tr><td><code>system_os</code></td><td>No</td><td>Target OS.</td></tr>
<tr><td><code>cpu</code></td><td>No</td><td>Target CPU architecture.</td></tr>
</table>
</article>
</section>
<section id="debug">
<h2>Debug</h2>
<article class="endpoint">
<div class="endpoint-header"><span class="method get">GET</span><span class="path">/api/_debug/detected_clients</span></div>
<p>Returns hostnames detected by Zeroconf.</p>
<div class="note">Development/debug only, with an inline comment saying it probably should not ship.</div>
</article>
<article class="endpoint">
<div class="endpoint-header"><span class="method post">POST</span><span class="path">/api/_debug/clear_history</span></div>
<p>Clears render queue history and returns <code>success</code>.</p>
<div class="note">Development/debug only.</div>
</article>
</section>
<section id="cleanup">
<h2>Cleanup Review</h2>
<div class="panel">
<h3>Routes With No In-Repo Callers Found</h3>
<ul>
<li><code>GET /api/jobs/status/&lt;status_val&gt;</code></li>
<li><code>GET /api/presets</code></li>
<li><code>GET /api/disk_benchmark</code></li>
<li><code>GET /api/engines/&lt;engine_name&gt;/args</code></li>
<li><code>GET /api/engines/download_available</code></li>
<li><code>GET /api/engines/most_recent_version</code></li>
<li><code>POST /api/engines/download</code></li>
<li><code>GET /api/_debug/detected_clients</code></li>
<li><code>POST /api/_debug/clear_history</code></li>
</ul>
</div>
<div class="panel" style="margin-top: 14px;">
<h3>Cleanup Risks</h3>
<ul>
<li><code>job_thumbnail()</code> parses <code>size=big</code> but never uses the resulting <code>big_thumb</code> value.</li>
</ul>
</div>
</section>
</div>
</main>
</body>
</html>