feat: ASRX hybrid pipeline, identity history, worker fixes, checkpoint system

This commit is contained in:
Accusys
2026-06-02 07:13:23 +08:00
parent e3066c3f49
commit e1572907ae
198 changed files with 43705 additions and 8910 deletions

View File

@@ -0,0 +1,358 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Api Access - Momentry API Docs</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; padding: 40px; }
.container { max-width: 960px; margin: 0 auto; background: white; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); padding: 40px; }
h1 { font-size: 24px; margin: 24px 0 12px; }
h2 { font-size: 20px; margin: 20px 0 10px; color: #222; }
h3 { font-size: 16px; margin: 16px 0 8px; color: #444; }
p { line-height: 1.6; margin: 8px 0; }
table { border-collapse: collapse; width: 100%; margin: 12px 0; font-size: 14px; }
th, td { border: 1px solid #ddd; padding: 8px 12px; text-align: left; }
th { background: #f0f0f0; font-weight: 600; }
code { background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-size: 13px; }
pre { background: #f8f8f8; border: 1px solid #ddd; border-radius: 6px; padding: 12px; overflow-x: auto; margin: 12px 0; }
pre code { background: none; padding: 0; }
a { color: #0066cc; }
.back { display: inline-block; margin-bottom: 20px; color: #666; }
.back:hover { color: #333; }
</style>
</head>
<body>
<div class="container">
<a class="back" href="index.html">&larr; Back to index</a>
<h1>Momentry Core API 存取指南</h1>
<table class="table">
<thead>
<tr>
<th>項目</th>
<th>內容</th>
</tr>
</thead>
<tbody>
<tr>
<td>版本</td>
<td>V1.3</td>
</tr>
<tr>
<td>日期</td>
<td>2026-03-25</td>
</tr>
<tr>
<td>用途</td>
<td>API 存取方式、端點與整合指南</td>
</tr>
</tbody>
</table>
<hr />
<h2>版本歷史</h2>
<table class="table">
<thead>
<tr>
<th>版本</th>
<th>日期</th>
<th>目的</th>
<th>操作人</th>
<th>工具/模型</th>
</tr>
</thead>
<tbody>
<tr>
<td>V1.3</td>
<td>2026-03-25</td>
<td>更新: n8n 搜尋回傳 <code>file_path</code> 取代 <code>media_url</code>,新增 API Key 驗證說明</td>
<td>OpenCode</td>
<td>deepseek-reasoner</td>
</tr>
<tr>
<td>V1.2</td>
<td>2026-03-24</td>
<td>更新網址與服務列表</td>
<td>Warren</td>
<td>OpenCode / MiniMax M2.5</td>
</tr>
<tr>
<td>V1.1</td>
<td>2026-03-23</td>
<td>初始版本</td>
<td>Warren</td>
<td>OpenCode / MiniMax M2.5</td>
</tr>
</tbody>
</table>
<hr />
<h2>基本網址</h2>
<table class="table">
<thead>
<tr>
<th>環境</th>
<th>URL</th>
<th>說明</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>本地開發</strong></td>
<td><code>http://localhost:3002</code></td>
<td>直接訪問 API繞過反向代理</td>
</tr>
<tr>
<td><strong>外部訪問</strong></td>
<td><code>https://m5api.momentry.ddns.net</code></td>
<td>通過 Caddy 反向代理訪問,需網路可達</td>
</tr>
</tbody>
</table>
<h3>何時使用哪個 URL</h3>
<p><strong>使用 <code>localhost:3002</code></strong>
- 開發/測試環境
- 直接在伺服器上操作
- 當反向代理有問題時</p>
<p><strong>使用 <code>m5api.momentry.ddns.net</code></strong>
- n8n workflow 中呼叫 API
- 外部系統整合
- 生產環境</p>
<h2>認證</h2>
<p>所有 <code>/api/v1/*</code> 端點(除了健康檢查 <code>/health</code><code>/health/detailed</code>)都需要 API Key 認證。</p>
<p>請在請求標頭中加入:</p>
<div class="codehilite"><pre><span></span><code>X-API-Key: YOUR_API_KEY
</code></pre></div>
<p><strong>目前示範使用的 API Key</strong>: <code>demo_api_key_12345</code></p>
<blockquote>
<p><strong>注意</strong>: 正式環境請使用安全的 API Key 管理機制,避免在客戶端暴露 API Key。</p>
</blockquote>
<hr />
<h2>影片搜尋 API</h2>
<h3>語意搜尋</h3>
<p><strong>端點:</strong> <code>POST /api/v1/search</code></p>
<p><strong>請求:</strong></p>
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;query&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;charade&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;limit&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;uuid&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;a1b10138a6bbb0cd&quot;</span>
<span class="p">}</span>
</code></pre></div>
<table class="table">
<thead>
<tr>
<th>欄位</th>
<th>類型</th>
<th>必填</th>
<th>說明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>query</code></td>
<td>字串</td>
<td></td>
<td>搜尋文字</td>
</tr>
<tr>
<td><code>limit</code></td>
<td>整數</td>
<td></td>
<td>最大回傳結果數(預設 10</td>
</tr>
<tr>
<td><code>uuid</code></td>
<td>字串</td>
<td></td>
<td>依影片 UUID 過濾</td>
</tr>
</tbody>
</table>
<p><strong>回應:</strong></p>
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;results&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;uuid&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;a1b10138a6bbb0cd&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;chunk_id&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;sentence_0006&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;chunk_type&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;sentence&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;start_time&quot;</span><span class="p">:</span><span class="w"> </span><span class="mf">48.8</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;end_time&quot;</span><span class="p">:</span><span class="w"> </span><span class="mf">55.44</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;text&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;fun plot twists, Woody Dialog and charming performances...&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;score&quot;</span><span class="p">:</span><span class="w"> </span><span class="mf">0.526</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="p">],</span>
<span class="w"> </span><span class="nt">&quot;query&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;charade&quot;</span>
<span class="p">}</span>
</code></pre></div>
<hr />
<h3>n8n 整合搜尋</h3>
<p><strong>端點:</strong> <code>POST /api/v1/n8n/search</code></p>
<p><strong>請求:</strong></p>
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;query&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;charade&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;limit&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span>
<span class="p">}</span>
</code></pre></div>
<p><strong>回應:</strong></p>
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;query&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;charade&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;count&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;hits&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;id&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;sentence_0006&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;vid&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;a1b10138a6bbb0cd&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;start&quot;</span><span class="p">:</span><span class="w"> </span><span class="mf">48.8</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;end&quot;</span><span class="p">:</span><span class="w"> </span><span class="mf">55.44</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;title&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Chunk sentence_0006&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;text&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;fun plot twists...&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;score&quot;</span><span class="p">:</span><span class="w"> </span><span class="mf">0.526</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;file_path&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;/Users/accusys/momentry/var/sftpgo/data/demo/Old_Time_Movie_Show_-_Charade_1963.HD.mov&quot;</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="p">]</span>
<span class="p">}</span>
</code></pre></div>
<blockquote>
<p><strong>注意</strong>: API 現在返回 <code>file_path</code>(檔案系統路徑)而非 <code>media_url</code>(網頁 URL。如需在網頁中播放影片請將檔案路徑轉換為可訪問的 URL例如透過 SFTPGo 分享連結)。</p>
</blockquote>
<hr />
<h2>影片管理 API</h2>
<h3>列出所有影片</h3>
<p><strong>端點:</strong> <code>GET /api/v1/videos</code></p>
<h3>查詢影片資訊</h3>
<p><strong>端點:</strong> <code>GET /api/v1/lookup?uuid={uuid}</code><code>GET /api/v1/lookup?path={path}</code></p>
<h3>取得處理進度</h3>
<p><strong>端點:</strong> <code>GET /api/v1/progress/{uuid}</code></p>
<hr />
<h2>區塊資料結構</h2>
<p>每個搜尋結果包含影片播放的時間資訊:</p>
<table class="table">
<thead>
<tr>
<th>欄位</th>
<th>說明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>uuid</code></td>
<td>影片識別碼</td>
</tr>
<tr>
<td><code>chunk_id</code></td>
<td>區塊唯一識別碼</td>
</tr>
<tr>
<td><code>chunk_type</code></td>
<td>類型:<code>sentence</code><code>cut</code><code>time_based</code></td>
</tr>
<tr>
<td><code>start_time</code></td>
<td>開始時間(秒)</td>
</tr>
<tr>
<td><code>end_time</code></td>
<td>結束時間(秒)</td>
</tr>
<tr>
<td><code>text</code></td>
<td>語音轉文字內容</td>
</tr>
<tr>
<td><code>score</code></td>
<td>相關性分數0-1</td>
</tr>
</tbody>
</table>
<hr />
<h2>整合範例</h2>
<h3>JavaScript/fetch</h3>
<div class="codehilite"><pre><span></span><code><span class="kd">const</span><span class="w"> </span><span class="nx">response</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">fetch</span><span class="p">(</span><span class="s1">&#39;http://localhost:3002/api/v1/search&#39;</span><span class="p">,</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nx">method</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;POST&#39;</span><span class="p">,</span>
<span class="w"> </span><span class="nx">headers</span><span class="o">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span>
<span class="w"> </span><span class="s1">&#39;Content-Type&#39;</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;application/json&#39;</span><span class="p">,</span>
<span class="w"> </span><span class="s1">&#39;X-API-Key&#39;</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;YOUR_API_KEY&#39;</span><span class="w"> </span><span class="c1">// 替換為實際的 API Key</span>
<span class="w"> </span><span class="p">},</span>
<span class="w"> </span><span class="nx">body</span><span class="o">:</span><span class="w"> </span><span class="nb">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">({</span><span class="w"> </span><span class="nx">query</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;charade&#39;</span><span class="p">,</span><span class="w"> </span><span class="nx">limit</span><span class="o">:</span><span class="w"> </span><span class="mf">5</span><span class="w"> </span><span class="p">})</span>
<span class="p">});</span>
<span class="kd">const</span><span class="w"> </span><span class="nx">data</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">response</span><span class="p">.</span><span class="nx">json</span><span class="p">();</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="nx">results</span><span class="p">);</span>
</code></pre></div>
<h3>PHP/cURL</h3>
<div class="codehilite"><pre><span></span><code><span class="x">$ch = curl_init(&#39;http://localhost:3002/api/v1/search&#39;);</span>
<span class="x">curl_setopt($ch, CURLOPT_POST, true);</span>
<span class="x">curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([</span>
<span class="x"> &#39;query&#39; =&gt; &#39;charade&#39;,</span>
<span class="x"> &#39;limit&#39; =&gt; 5</span>
<span class="x">]));</span>
<span class="x">curl_setopt($ch, CURLOPT_HTTPHEADER, [</span>
<span class="x"> &#39;Content-Type: application/json&#39;,</span>
<span class="x"> &#39;X-API-Key: YOUR_API_KEY&#39; // 替換為實際的 API Key</span>
<span class="x">]);</span>
<span class="x">$response = curl_exec($ch);</span>
<span class="x">$data = json_decode($response, true);</span>
</code></pre></div>
<hr />
<h2>影片嵌入網址</h2>
<blockquote>
<p><strong>重要</strong>: API 現在返回 <code>file_path</code>(檔案系統路徑),而非直接可訪問的網址。您需要將檔案路徑轉換為 SFTPGo 分享連結才能嵌入影片。</p>
</blockquote>
<p><strong>檔案路徑轉換為網址:</strong>
- API 返回的 <code>file_path</code> 範例:<code>/Users/accusys/momentry/var/sftpgo/data/demo/video.mp4</code>
- 對應的 SFTPGo 分享連結:<code>https://wp.momentry.ddns.net/demo/video.mp4</code>
- 轉換方式:移除 <code>/Users/accusys/momentry/var/sftpgo/data/</code> 前綴,將剩餘路徑附加到 <code>https://wp.momentry.ddns.net/</code></p>
<p><strong>手動建立分享連結:</strong>
1. 開啟 SFTPGo Web UI<code>http://localhost:8080</code>
2. 使用帳號 <code>demo</code> / 密碼 <code>demopassword123</code> 登入
3. 導航至 <code>Files</code> → 選擇影片檔案
4. 點擊 <code>Share</code><code>Create Link</code>
5. 複製產生的分享連結</p>
<p>使用搜尋結果中的 <code>start_time</code><code>end_time</code> 來嵌入影片片段。</p>
<hr />
<h2>服務列表</h2>
<table class="table">
<thead>
<tr>
<th>服務</th>
<th>網址</th>
<th>用途</th>
</tr>
</thead>
<tbody>
<tr>
<td>Momentry API</td>
<td><code>http://localhost:3002</code></td>
<td>核心 API</td>
</tr>
<tr>
<td>SFTPGo</td>
<td><code>http://localhost:8080</code></td>
<td>檔案儲存</td>
</tr>
<tr>
<td>Qdrant</td>
<td><code>http://localhost:6333</code></td>
<td>向量搜尋</td>
</tr>
<tr>
<td>PostgreSQL</td>
<td><code>localhost:5432</code></td>
<td>資料庫</td>
</tr>
</tbody>
</table>
<hr />
<h2>示範影片</h2>
<ul>
<li><strong>檔案:</strong> <code>Old_Time_Movie_Show_-_Charade_1963.HD.mov</code></li>
<li><strong>UUID</strong> <code>a1b10138a6bbb0cd</code></li>
<li><strong>長度:</strong> 約 6879 秒(約 1.9 小時)</li>
<li><strong>區塊數:</strong> 3886 個(句子 + 場景 + 時間)</li>
</ul>
</div>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,207 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Api Error Codes - Momentry API Docs</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; padding: 40px; }
.container { max-width: 960px; margin: 0 auto; background: white; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); padding: 40px; }
h1 { font-size: 24px; margin: 24px 0 12px; }
h2 { font-size: 20px; margin: 20px 0 10px; color: #222; }
h3 { font-size: 16px; margin: 16px 0 8px; color: #444; }
p { line-height: 1.6; margin: 8px 0; }
table { border-collapse: collapse; width: 100%; margin: 12px 0; font-size: 14px; }
th, td { border: 1px solid #ddd; padding: 8px 12px; text-align: left; }
th { background: #f0f0f0; font-weight: 600; }
code { background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-size: 13px; }
pre { background: #f8f8f8; border: 1px solid #ddd; border-radius: 6px; padding: 12px; overflow-x: auto; margin: 12px 0; }
pre code { background: none; padding: 0; }
a { color: #0066cc; }
.back { display: inline-block; margin-bottom: 20px; color: #666; }
.back:hover { color: #333; }
</style>
</head>
<body>
<div class="container">
<a class="back" href="index.html">&larr; Back to index</a>
<hr />
<p>document_type: "api_reference"
service: "MOMENTRY_CORE"
title: "API Error Codes (API 標準錯誤碼)"
date: "2026-05-17"
version: "V1.1"
status: "active"
owner: "M5"
created_by: "OpenCode"</p>
<hr />
<h1>API Error Codes (API 標準錯誤碼)</h1>
<table class="table">
<thead>
<tr>
<th>項目</th>
<th>內容</th>
</tr>
</thead>
<tbody>
<tr>
<td>目標讀者</td>
<td>developer</td>
</tr>
<tr>
<td>預備知識</td>
<td>需有 API Key</td>
</tr>
</tbody>
</table>
<hr />
<h2>Error Response Format</h2>
<p>All API errors follow this JSON structure:</p>
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;success&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;error&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;code&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;E001_NOT_FOUND&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;message&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Resource not found&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;details&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nt">&quot;resource&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;file_uuid&quot;</span><span class="p">,</span><span class="w"> </span><span class="nt">&quot;value&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;abc&quot;</span><span class="p">}</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<h2>Error Code List</h2>
<h3>Generic Errors (E0xx)</h3>
<table class="table">
<thead>
<tr>
<th>Code</th>
<th>HTTP</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>E001_NOT_FOUND</code></td>
<td>404</td>
<td>Resource not found (file, identity, chunk)</td>
</tr>
<tr>
<td><code>E002_DUPLICATE</code></td>
<td>409</td>
<td>Resource already exists</td>
</tr>
<tr>
<td><code>E003_VALIDATION</code></td>
<td>400</td>
<td>Request parameter validation failed</td>
</tr>
<tr>
<td><code>E004_UNAUTHORIZED</code></td>
<td>401</td>
<td>Invalid API key or token</td>
</tr>
<tr>
<td><code>E005_INTERNAL</code></td>
<td>500</td>
<td>Internal server error</td>
</tr>
</tbody>
</table>
<h3>Processor Errors (E1xx)</h3>
<table class="table">
<thead>
<tr>
<th>Code</th>
<th>HTTP</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>E101_PROCESSOR_FAIL</code></td>
<td>500</td>
<td>Python script execution failed</td>
</tr>
<tr>
<td><code>E102_TIMEOUT</code></td>
<td>504</td>
<td>Processing timeout</td>
</tr>
<tr>
<td><code>E103_RESUME_FAIL</code></td>
<td>500</td>
<td>Resume failed (checkpoint not found)</td>
</tr>
<tr>
<td><code>E104_NO_VIDEO</code></td>
<td>400</td>
<td>Video file path not found</td>
</tr>
</tbody>
</table>
<h3>Identity Errors (E2xx)</h3>
<table class="table">
<thead>
<tr>
<th>Code</th>
<th>HTTP</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>E201_FACE_NOT_FOUND</code></td>
<td>404</td>
<td>Face detection not found</td>
</tr>
<tr>
<td><code>E202_MERGE_CONFLICT</code></td>
<td>409</td>
<td>Identity merge conflict</td>
</tr>
<tr>
<td><code>E203_CANDIDATE_EMPTY</code></td>
<td>404</td>
<td>No candidates available for confirmation</td>
</tr>
</tbody>
</table>
<h3>TMDb Errors (E3xx)</h3>
<table class="table">
<thead>
<tr>
<th>Code</th>
<th>HTTP</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>E301_TMDB_NO_KEY</code></td>
<td>400</td>
<td><code>TMDB_API_KEY</code> environment variable not set</td>
</tr>
<tr>
<td><code>E302_TMDB_UNREACHABLE</code></td>
<td>502</td>
<td>TMDb API unreachable or timed out</td>
</tr>
<tr>
<td><code>E303_TMDB_CACHE_NOT_FOUND</code></td>
<td>200</td>
<td>No local TMDb cache; run prefetch first</td>
</tr>
<tr>
<td><code>E304_TMDB_PROBE_FAILED</code></td>
<td>500</td>
<td>TMDb probe execution failed</td>
</tr>
<tr>
<td><code>E305_TMDB_MOVIE_NOT_FOUND</code></td>
<td>404</td>
<td>No matching TMDb movie found from filename</td>
</tr>
</tbody>
</table>
<hr />
</div>
</body>
</html>

View File

@@ -0,0 +1,125 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Api Index - Momentry API Docs</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; padding: 40px; }
.container { max-width: 960px; margin: 0 auto; background: white; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); padding: 40px; }
h1 { font-size: 24px; margin: 24px 0 12px; }
h2 { font-size: 20px; margin: 20px 0 10px; color: #222; }
h3 { font-size: 16px; margin: 16px 0 8px; color: #444; }
p { line-height: 1.6; margin: 8px 0; }
table { border-collapse: collapse; width: 100%; margin: 12px 0; font-size: 14px; }
th, td { border: 1px solid #ddd; padding: 8px 12px; text-align: left; }
th { background: #f0f0f0; font-weight: 600; }
code { background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-size: 13px; }
pre { background: #f8f8f8; border: 1px solid #ddd; border-radius: 6px; padding: 12px; overflow-x: auto; margin: 12px 0; }
pre code { background: none; padding: 0; }
a { color: #0066cc; }
.back { display: inline-block; margin-bottom: 20px; color: #666; }
.back:hover { color: #333; }
</style>
</head>
<body>
<div class="container">
<a class="back" href="index.html">&larr; Back to index</a>
<hr />
<p>document_type: "api_reference"
service: "MOMENTRY_CORE"
title: "Momentry Core API 文件總覽"
date: "2026-05-17"
version: "V1.0"
status: "active"
owner: "M5"
created_by: "OpenCode"</p>
<hr />
<h1>Momentry Core API 文件總覽</h1>
<table class="table">
<thead>
<tr>
<th>項目</th>
<th>內容</th>
</tr>
</thead>
<tbody>
<tr>
<td>目標讀者</td>
<td>developer</td>
</tr>
<tr>
<td>預備知識</td>
<td>需有 API Key</td>
</tr>
</tbody>
</table>
<hr />
<h2>📁 文件結構</h2>
<div class="codehilite"><pre><span></span><code><span class="n">API_WORKSPACE</span><span class="o">/</span>
<span class="err">└──</span><span class="w"> </span><span class="n">modules</span><span class="o">/</span>
<span class="err"></span><span class="w"> </span><span class="err">├──</span><span class="w"> </span><span class="n">_template</span><span class="p">.</span><span class="n">md</span><span class="w"> </span><span class="err"></span><span class="w"> </span><span class="n">One</span><span class="o">-</span><span class="n">line</span><span class="w"> </span><span class="n">description</span><span class="w"> </span><span class="n">of</span><span class="w"> </span><span class="n">what</span><span class="w"> </span><span class="n">this</span><span class="w"> </span><span class="k">module</span><span class="w"> </span><span class="n">covers</span>
<span class="err"></span><span class="w"> </span><span class="err">├──</span><span class="w"> </span><span class="mh">01</span><span class="n">_auth</span><span class="p">.</span><span class="n">md</span><span class="w"> </span><span class="err"></span><span class="w"> </span><span class="n">Authentication</span><span class="w"> </span><span class="err"></span><span class="w"> </span><span class="n">login</span><span class="p">,</span><span class="w"> </span><span class="n">logout</span><span class="p">,</span><span class="w"> </span><span class="n">JWT</span><span class="p">,</span><span class="w"> </span><span class="n">session</span><span class="w"> </span><span class="n">cookie</span><span class="p">,</span><span class="w"> </span><span class="n">API</span><span class="w"> </span><span class="n">key</span>
<span class="err"></span><span class="w"> </span><span class="err">├──</span><span class="w"> </span><span class="mh">02</span><span class="n">_health</span><span class="p">.</span><span class="n">md</span><span class="w"> </span><span class="err"></span><span class="w"> </span><span class="n">Health</span><span class="w"> </span><span class="n">check</span><span class="w"> </span><span class="n">endpoints</span>
<span class="err"></span><span class="w"> </span><span class="err">├──</span><span class="w"> </span><span class="mh">03</span><span class="n">_register</span><span class="p">.</span><span class="n">md</span><span class="w"> </span><span class="err"></span><span class="w"> </span><span class="n">File</span><span class="w"> </span><span class="n">registration</span><span class="w"> </span><span class="err"></span><span class="w"> </span><span class="n">register</span><span class="p">,</span><span class="w"> </span><span class="n">scan</span>
<span class="err"></span><span class="w"> </span><span class="err">├──</span><span class="w"> </span><span class="mh">04</span><span class="n">_lookup</span><span class="p">.</span><span class="n">md</span><span class="w"> </span><span class="err"></span><span class="w"> </span><span class="n">File</span><span class="w"> </span><span class="n">lookup</span><span class="w"> </span><span class="n">by</span><span class="w"> </span><span class="n">name</span><span class="w"> </span><span class="k">and</span><span class="w"> </span><span class="n">unregistration</span>
<span class="err"></span><span class="w"> </span><span class="err">├──</span><span class="w"> </span><span class="mh">05</span><span class="n">_process</span><span class="p">.</span><span class="n">md</span><span class="w"> </span><span class="err"></span><span class="w"> </span><span class="n">Processing</span><span class="w"> </span><span class="n">pipeline</span><span class="w"> </span><span class="err"></span><span class="w"> </span><span class="n">trigger</span><span class="p">,</span><span class="w"> </span><span class="n">probe</span><span class="p">,</span><span class="w"> </span><span class="n">progress</span><span class="p">,</span><span class="w"> </span><span class="n">jobs</span>
<span class="err"></span><span class="w"> </span><span class="err">├──</span><span class="w"> </span><span class="mh">06</span><span class="n">_search</span><span class="p">.</span><span class="n">md</span><span class="w"> </span><span class="err"></span><span class="w"> </span><span class="n">Vector</span><span class="w"> </span><span class="n">search</span><span class="p">,</span><span class="w"> </span><span class="n">hybrid</span><span class="w"> </span><span class="n">search</span><span class="p">,</span><span class="w"> </span><span class="n">BM25</span><span class="p">,</span><span class="w"> </span><span class="n">n8n</span><span class="p">,</span><span class="w"> </span><span class="n">visual</span><span class="p">,</span><span class="w"> </span><span class="n">identity</span><span class="w"> </span><span class="n">text</span><span class="w"> </span><span class="n">search</span>
<span class="err"></span><span class="w"> </span><span class="err">├──</span><span class="w"> </span><span class="mh">07</span><span class="n">_identity</span><span class="p">.</span><span class="n">md</span><span class="w"> </span><span class="err"></span><span class="w"> </span><span class="n">Global</span><span class="w"> </span><span class="n">identities</span><span class="w"> </span><span class="err"></span><span class="w"> </span><span class="n">CRUD</span><span class="p">,</span><span class="w"> </span><span class="n">detail</span><span class="p">,</span><span class="w"> </span><span class="n">files</span><span class="p">,</span><span class="w"> </span><span class="n">faces</span><span class="p">,</span><span class="w"> </span><span class="n">bind</span><span class="p">,</span><span class="w"> </span><span class="n">unbind</span><span class="p">,</span><span class="w"> </span><span class="n">search</span>
<span class="err"></span><span class="w"> </span><span class="err">├──</span><span class="w"> </span><span class="mh">08</span><span class="n">_identity_agent</span><span class="p">.</span><span class="n">md</span><span class="w"> </span><span class="err"></span><span class="w"> </span><span class="n">Identity</span><span class="w"> </span><span class="n">agent</span><span class="w"> </span><span class="err"></span><span class="w"> </span><span class="n">analyze</span><span class="p">,</span><span class="w"> </span><span class="n">suggest</span><span class="p">,</span><span class="w"> </span><span class="n">merge</span><span class="p">,</span><span class="w"> </span><span class="n">clustering</span>
<span class="err"></span><span class="w"> </span><span class="err">├──</span><span class="w"> </span><span class="mh">08</span><span class="n">_media</span><span class="p">.</span><span class="n">md</span><span class="w"> </span><span class="err"></span><span class="w"> </span><span class="n">Video</span><span class="w"> </span><span class="n">streaming</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="n">frame</span><span class="w"> </span><span class="n">extraction</span>
<span class="err"></span><span class="w"> </span><span class="err">├──</span><span class="w"> </span><span class="mh">09</span><span class="n">_tmdb</span><span class="p">.</span><span class="n">md</span><span class="w"> </span><span class="err"></span><span class="w"> </span><span class="n">TMDb</span><span class="w"> </span><span class="n">enrichment</span><span class="w"> </span><span class="n">endpoints</span><span class="w"> </span><span class="err"></span><span class="w"> </span><span class="n">prefetch</span><span class="p">,</span><span class="w"> </span><span class="n">probe</span><span class="p">,</span><span class="w"> </span><span class="n">resource</span><span class="p">,</span><span class="w"> </span><span class="n">check</span>
<span class="err"></span><span class="w"> </span><span class="err">├──</span><span class="w"> </span><span class="mh">10</span><span class="n">_pipeline</span><span class="p">.</span><span class="n">md</span><span class="w"> </span><span class="err"></span><span class="w"> </span><span class="n">Stats</span><span class="w"> </span><span class="n">endpoints</span><span class="p">,</span><span class="w"> </span><span class="n">inference</span><span class="w"> </span><span class="n">health</span><span class="p">,</span><span class="w"> </span><span class="n">stfpgo</span><span class="w"> </span><span class="n">status</span>
<span class="err"></span><span class="w"> </span><span class="err">├──</span><span class="w"> </span><span class="mh">11</span><span class="n">_error_codes</span><span class="p">.</span><span class="n">md</span><span class="w"> </span><span class="err"></span><span class="w"> </span><span class="n">Standard</span><span class="w"> </span><span class="n">API</span><span class="w"> </span><span class="n">error</span><span class="w"> </span><span class="n">codes</span>
<span class="err"></span><span class="w"> </span><span class="err">├──</span><span class="w"> </span><span class="mh">12</span><span class="n">_agent</span><span class="p">.</span><span class="n">md</span><span class="w"> </span><span class="err"></span><span class="w"> </span>
<span class="err">└──</span><span class="w"> </span><span class="p">(</span><span class="n">generated</span><span class="w"> </span><span class="n">files</span><span class="w"> </span><span class="err"></span><span class="w"> </span><span class="n">GUIDES</span><span class="o">/</span><span class="p">)</span>
</code></pre></div>
<h2>快速選擇指南</h2>
<table class="table">
<thead>
<tr>
<th>需求</th>
<th>閱讀文件</th>
</tr>
</thead>
<tbody>
<tr>
<td>查看所有 API 端點curl 範例版)</td>
<td><code>GUIDES/API_ENDPOINTS.md</code></td>
</tr>
<tr>
<td>查看快速端點摘要</td>
<td><code>GUIDES/API_QUICK_REFERENCE.md</code></td>
</tr>
<tr>
<td>執行 TMDb Enrichment</td>
<td><code>GUIDES/TMDb_User_Guide.md</code></td>
</tr>
<tr>
<td>查看錯誤碼</td>
<td><code>GUIDES/API_ERROR_CODES.md</code></td>
</tr>
</tbody>
</table>
<h2>文件模組清單</h2>
<ul>
<li><code>_template</code> — One-line description of what this module covers</li>
<li><code>01_auth</code> — Authentication — login, logout, JWT, session cookie, API key</li>
<li><code>02_health</code> — Health check endpoints</li>
<li><code>03_register</code> — File registration — register, scan</li>
<li><code>04_lookup</code> — File lookup by name and unregistration</li>
<li><code>05_process</code> — Processing pipeline — trigger, probe, progress, jobs</li>
<li><code>06_search</code> — Vector search, hybrid search, BM25, n8n, visual, identity text search</li>
<li><code>07_identity</code> — Global identities — CRUD, detail, files, faces, bind, unbind, search</li>
<li><code>08_identity_agent</code> — Identity agent — analyze, suggest, merge, clustering</li>
<li><code>08_media</code> — Video streaming &amp; frame extraction</li>
<li><code>09_tmdb</code> — TMDb enrichment endpoints — prefetch, probe, resource, check</li>
<li><code>10_pipeline</code> — Stats endpoints, inference health, stfpgo status</li>
<li><code>11_error_codes</code> — Standard API error codes</li>
<li><code>12_agent</code></li>
</ul>
<hr />
</div>
</body>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,472 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>M5Api Pipeline Demo - Momentry API Docs</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; padding: 40px; }
.container { max-width: 960px; margin: 0 auto; background: white; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); padding: 40px; }
h1 { font-size: 24px; margin: 24px 0 12px; }
h2 { font-size: 20px; margin: 20px 0 10px; color: #222; }
h3 { font-size: 16px; margin: 16px 0 8px; color: #444; }
p { line-height: 1.6; margin: 8px 0; }
table { border-collapse: collapse; width: 100%; margin: 12px 0; font-size: 14px; }
th, td { border: 1px solid #ddd; padding: 8px 12px; text-align: left; }
th { background: #f0f0f0; font-weight: 600; }
code { background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-size: 13px; }
pre { background: #f8f8f8; border: 1px solid #ddd; border-radius: 6px; padding: 12px; overflow-x: auto; margin: 12px 0; }
pre code { background: none; padding: 0; }
a { color: #0066cc; }
.back { display: inline-block; margin-bottom: 20px; color: #666; }
.back:hover { color: #333; }
</style>
</head>
<body>
<div class="container">
<a class="back" href="index.html">&larr; Back to index</a>
<hr />
<p>document_type: "demo_guide"
service: "MOMENTRY_CORE"
title: "M5API Pipeline Demo"
date: "2026-05-16"
version: "V1.0"
status: "active"
owner: "M5"
created_by: "OpenCode"
tags:
- "demo"
- "pipeline"
- "api"
- "m5api"
ai_query_hints:
- "M5API Pipeline demo"
- "如何透過 M5 的 API 執行 Pipeline"
related_documents:
- "GUIDES/Demo_EndToEnd.md"
- "GUIDES/API_ENDPOINTS.md"</p>
<hr />
<h1>Momentry Core — M5API Pipeline Demo</h1>
<table class="table">
<thead>
<tr>
<th>項目</th>
<th>內容</th>
</tr>
</thead>
<tbody>
<tr>
<td>建立者</td>
<td>OpenCode</td>
</tr>
<tr>
<td>建立時間</td>
<td>2026-05-16</td>
</tr>
<tr>
<td>文件版本</td>
<td>V1.0</td>
</tr>
<tr>
<td>目標讀者</td>
<td>developer</td>
</tr>
<tr>
<td>預備知識</td>
<td>需有 API Key、M5 服務已啟動</td>
</tr>
</tbody>
</table>
<hr />
<h2>Prerequisites</h2>
<div class="codehilite"><pre><span></span><code><span class="nv">API</span><span class="o">=</span><span class="s2">&quot;https://m5api.momentry.ddns.net&quot;</span>
<span class="nv">KEY</span><span class="o">=</span><span class="s2">&quot;muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69&quot;</span>
</code></pre></div>
<hr />
<h2>Step 1: System Health Check</h2>
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-sf<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/health&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span><span class="s1">&#39;{ip, port, status, version, build_git_hash}&#39;</span>
</code></pre></div>
<p>Response:</p>
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;ip&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;192.168.110.201&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;port&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">3002</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;status&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;ok&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;version&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;1.0.0&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;build_git_hash&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;c41f7e0c&quot;</span>
<span class="p">}</span>
</code></pre></div>
<p>All core services verified:</p>
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-sf<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/health/detailed&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span><span class="s1">&#39;{</span>
<span class="s1"> services, schema: .schema.ok,</span>
<span class="s1"> scripts: .pipeline.scripts_count,</span>
<span class="s1"> integrity: .pipeline.scripts_integrity,</span>
<span class="s1"> procs: [.pipeline.processors | to_entries[] | select(.value==true and .key!=&quot;total_py_files&quot;) | .key]</span>
<span class="s1">}&#39;</span>
</code></pre></div>
<p>Response:</p>
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;services&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;postgres&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nt">&quot;status&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;ok&quot;</span><span class="p">},</span>
<span class="w"> </span><span class="nt">&quot;redis&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nt">&quot;status&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;ok&quot;</span><span class="p">},</span>
<span class="w"> </span><span class="nt">&quot;qdrant&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nt">&quot;status&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;ok&quot;</span><span class="p">},</span>
<span class="w"> </span><span class="nt">&quot;mongodb&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nt">&quot;status&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;ok&quot;</span><span class="p">}</span>
<span class="w"> </span><span class="p">},</span>
<span class="w"> </span><span class="nt">&quot;schema&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;scripts&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">286</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;integrity&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nt">&quot;matched&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">345</span><span class="p">,</span><span class="w"> </span><span class="nt">&quot;total&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">345</span><span class="p">,</span><span class="w"> </span><span class="nt">&quot;ok&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">},</span>
<span class="w"> </span><span class="nt">&quot;procs&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&quot;asr&quot;</span><span class="p">,</span><span class="s2">&quot;yolo&quot;</span><span class="p">,</span><span class="s2">&quot;face&quot;</span><span class="p">,</span><span class="s2">&quot;pose&quot;</span><span class="p">,</span><span class="s2">&quot;ocr&quot;</span><span class="p">,</span><span class="s2">&quot;cut&quot;</span><span class="p">,</span><span class="s2">&quot;caption&quot;</span><span class="p">,</span><span class="s2">&quot;scene&quot;</span><span class="p">,</span><span class="s2">&quot;story&quot;</span><span class="p">,</span><span class="s2">&quot;asrx&quot;</span><span class="p">,</span><span class="s2">&quot;probe&quot;</span><span class="p">,</span><span class="s2">&quot;visual_chunk&quot;</span><span class="p">]</span>
<span class="p">}</span>
</code></pre></div>
<hr />
<h2>Step 2: List Registered Files</h2>
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-sf<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;X-API-Key: </span><span class="nv">$KEY</span><span class="s2">&quot;</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/files?page=1&amp;page_size=5&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>jq<span class="w"> </span><span class="s1">&#39;{total, files: [.data[]? | {name: .file_name[0:50], status}]}&#39;</span>
</code></pre></div>
<p>Response:</p>
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;total&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">56</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;files&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>
<span class="w"> </span><span class="p">{</span><span class="nt">&quot;name&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Charade (1963) Cary Grant &amp; Audrey Hepburn ...&quot;</span><span class="p">,</span><span class="w"> </span><span class="nt">&quot;status&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;completed&quot;</span><span class="p">},</span>
<span class="w"> </span><span class="p">{</span><span class="nt">&quot;name&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;ExaSAN PCIe series - Director Ou Yu-Zhi ...&quot;</span><span class="p">,</span><span class="w"> </span><span class="nt">&quot;status&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;completed&quot;</span><span class="p">},</span>
<span class="w"> </span><span class="p">{</span><span class="nt">&quot;name&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Old_Time_Movie_Show_-_Charade_1963.HD.mov&quot;</span><span class="p">,</span><span class="w"> </span><span class="nt">&quot;status&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;completed&quot;</span><span class="p">},</span>
<span class="w"> </span><span class="p">{</span><span class="nt">&quot;name&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Old Felix the Cat Cartoon.mp4&quot;</span><span class="p">,</span><span class="w"> </span><span class="nt">&quot;status&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;unregistered&quot;</span><span class="p">},</span>
<span class="w"> </span><span class="p">{</span><span class="nt">&quot;name&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;short_clip.mov&quot;</span><span class="p">,</span><span class="w"> </span><span class="nt">&quot;status&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;completed&quot;</span><span class="p">}</span>
<span class="w"> </span><span class="p">]</span>
<span class="p">}</span>
</code></pre></div>
<hr />
<h2>Step 3: Register a New File</h2>
<div class="codehilite"><pre><span></span><code><span class="c1"># POST with file_path (must exist on server filesystem)</span>
curl<span class="w"> </span>-sf<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;X-API-Key: </span><span class="nv">$KEY</span><span class="s2">&quot;</span><span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;Content-Type: application/json&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>-d<span class="w"> </span><span class="s1">&#39;{&quot;file_path&quot;: &quot;/path/to/video.mp4&quot;}&#39;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/files/register&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span><span class="s1">&#39;{success, file_uuid, file_name, file_type, duration, fps, already_exists}&#39;</span>
</code></pre></div>
<p>Response (new registration):</p>
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;success&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;file_uuid&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;3abeee81d94597629ed8cb943f182e94&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;file_name&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Charade (1963) Cary Grant &amp; Audrey Hepburn ...mp4&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;file_type&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;video&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;duration&quot;</span><span class="p">:</span><span class="w"> </span><span class="mf">6785.014</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;fps&quot;</span><span class="p">:</span><span class="w"> </span><span class="mf">23.976</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;already_exists&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span>
<span class="p">}</span>
</code></pre></div>
<p>Response (duplicate content — SHA256 dedup):</p>
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;success&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;already_exists&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;message&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Content already registered (identical file)&quot;</span>
<span class="p">}</span>
</code></pre></div>
<hr />
<h2>Step 4: Probe (ffprobe Metadata)</h2>
<div class="codehilite"><pre><span></span><code><span class="nv">UUID</span><span class="o">=</span><span class="s2">&quot;3abeee81d94597629ed8cb943f182e94&quot;</span>
curl<span class="w"> </span>-sf<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;X-API-Key: </span><span class="nv">$KEY</span><span class="s2">&quot;</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/file/</span><span class="si">${</span><span class="nv">UUID</span><span class="si">}</span><span class="s2">/probe&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>jq<span class="w"> </span><span class="s1">&#39;{name: .file_name, video: &quot;\(.width)x\(.height)&quot;, fps, duration, cached, streams: [.streams[] | {type: .codec_type, codec: .codec_name}]}&#39;</span>
</code></pre></div>
<p>Response:</p>
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;name&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Charade (1963) Cary Grant &amp; Audrey Hepburn ...mp4&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;video&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;720x304&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;fps&quot;</span><span class="p">:</span><span class="w"> </span><span class="mf">23.976</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;duration&quot;</span><span class="p">:</span><span class="w"> </span><span class="mf">6785.014</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;cached&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;streams&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>
<span class="w"> </span><span class="p">{</span><span class="nt">&quot;type&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;video&quot;</span><span class="p">,</span><span class="w"> </span><span class="nt">&quot;codec&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;h264&quot;</span><span class="p">},</span>
<span class="w"> </span><span class="p">{</span><span class="nt">&quot;type&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;audio&quot;</span><span class="p">,</span><span class="w"> </span><span class="nt">&quot;codec&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;aac&quot;</span><span class="p">}</span>
<span class="w"> </span><span class="p">]</span>
<span class="p">}</span>
</code></pre></div>
<p>Error cases:</p>
<div class="codehilite"><pre><span></span><code><span class="c1"># Non-existent UUID</span>
curl<span class="w"> </span>-sf<span class="w"> </span><span class="s2">&quot;https://m5api.momentry.ddns.net/api/v1/file/bad_uuid/probe&quot;</span>
<span class="c1"># → {&quot;error&quot;:&quot;Video not found&quot;,&quot;file_uuid&quot;:&quot;bad_uuid&quot;} HTTP 404</span>
<span class="c1"># File deleted from disk</span>
<span class="c1"># → {&quot;error&quot;:&quot;File does not exist at registered path&quot;,&quot;file_uuid&quot;:&quot;...&quot;,&quot;file_path&quot;:&quot;...&quot;} HTTP 404</span>
</code></pre></div>
<hr />
<h2>Step 5: Submit Processing Job</h2>
<div class="codehilite"><pre><span></span><code><span class="c1"># Specific processors</span>
curl<span class="w"> </span>-sf<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;X-API-Key: </span><span class="nv">$KEY</span><span class="s2">&quot;</span><span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;Content-Type: application/json&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>-d<span class="w"> </span><span class="s1">&#39;{&quot;processors&quot;:[&quot;asr&quot;,&quot;cut&quot;,&quot;yolo&quot;,&quot;face&quot;,&quot;pose&quot;,&quot;ocr&quot;]}&#39;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/file/</span><span class="si">${</span><span class="nv">UUID</span><span class="si">}</span><span class="s2">/process&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span><span class="s1">&#39;{job_id, file_uuid: .file_uuid[0:16], status}&#39;</span>
</code></pre></div>
<p>Response:</p>
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;job_id&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">167</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;file_uuid&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;3abeee81d9459762&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;status&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;PENDING&quot;</span>
<span class="p">}</span>
</code></pre></div>
<blockquote>
<p><strong>All processors</strong>: Send <code>{}</code> (empty body) to run all 12 processors.
Available: <code>asr</code>, <code>cut</code>, <code>yolo</code>, <code>face</code>, <code>pose</code>, <code>ocr</code>, <code>asrx</code>, <code>visual_chunk</code>, <code>scene</code>, <code>story</code>, <code>caption</code></p>
</blockquote>
<hr />
<h2>Step 6: Monitor Progress</h2>
<div class="codehilite"><pre><span></span><code><span class="k">while</span><span class="w"> </span>true<span class="p">;</span><span class="w"> </span><span class="k">do</span>
<span class="w"> </span><span class="nv">PROGRESS</span><span class="o">=</span><span class="k">$(</span>curl<span class="w"> </span>-sf<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;X-API-Key: </span><span class="nv">$KEY</span><span class="s2">&quot;</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/progress/</span><span class="si">${</span><span class="nv">UUID</span><span class="si">}</span><span class="s2">&quot;</span><span class="k">)</span>
<span class="w"> </span><span class="nv">STATUS</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$PROGRESS</span><span class="s2">&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span>-r<span class="w"> </span><span class="s1">&#39;.status // &quot;?&quot;&#39;</span><span class="k">)</span>
<span class="w"> </span><span class="nv">PROCS</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$PROGRESS</span><span class="s2">&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span>-r<span class="w"> </span><span class="s1">&#39;[.processors[]? | &quot;\(.name)=\(.status)(\(.frames_processed))&quot;] | join(&quot; &quot;)&#39;</span><span class="k">)</span>
<span class="w"> </span><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;</span><span class="k">$(</span>date<span class="w"> </span>+%H:%M:%S<span class="k">)</span><span class="s2">: </span><span class="nv">$PROCS</span><span class="s2">&quot;</span>
<span class="w"> </span><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$PROCS</span><span class="s2">&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>-q<span class="w"> </span><span class="s2">&quot;completed&quot;</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="k">break</span>
<span class="w"> </span>sleep<span class="w"> </span><span class="m">10</span>
<span class="k">done</span>
</code></pre></div>
<p>Typical output:</p>
<div class="codehilite"><pre><span></span><code><span class="mf">12</span><span class="p">:</span><span class="mf">30</span><span class="p">:</span><span class="mf">01</span><span class="p">:</span><span class="w"> </span><span class="n">asr</span><span class="o">=</span><span class="n">pending</span><span class="p">(</span><span class="mf">0</span><span class="p">)</span><span class="w"> </span><span class="n">cut</span><span class="o">=</span><span class="n">pending</span><span class="p">(</span><span class="mf">0</span><span class="p">)</span><span class="w"> </span><span class="n">yolo</span><span class="o">=</span><span class="n">pending</span><span class="p">(</span><span class="mf">0</span><span class="p">)</span><span class="w"> </span><span class="n">face</span><span class="o">=</span><span class="n">pending</span><span class="p">(</span><span class="mf">0</span><span class="p">)</span><span class="w"> </span><span class="nb">pos</span><span class="n">e</span><span class="o">=</span><span class="n">pending</span><span class="p">(</span><span class="mf">0</span><span class="p">)</span><span class="w"> </span><span class="n">ocr</span><span class="o">=</span><span class="n">pending</span><span class="p">(</span><span class="mf">0</span><span class="p">)</span>
<span class="mf">12</span><span class="p">:</span><span class="mf">30</span><span class="p">:</span><span class="mf">11</span><span class="p">:</span><span class="w"> </span><span class="n">asr</span><span class="o">=</span><span class="kr">run</span><span class="n">ning</span><span class="p">(</span><span class="mf">0</span><span class="p">)</span><span class="w"> </span><span class="n">cut</span><span class="o">=</span><span class="kr">run</span><span class="n">ning</span><span class="p">(</span><span class="mf">0</span><span class="p">)</span><span class="w"> </span><span class="n">yolo</span><span class="o">=</span><span class="n">pending</span><span class="p">(</span><span class="mf">0</span><span class="p">)</span><span class="w"> </span><span class="n">face</span><span class="o">=</span><span class="n">pending</span><span class="p">(</span><span class="mf">0</span><span class="p">)</span><span class="w"> </span><span class="nb">pos</span><span class="n">e</span><span class="o">=</span><span class="n">pending</span><span class="p">(</span><span class="mf">0</span><span class="p">)</span><span class="w"> </span><span class="n">ocr</span><span class="o">=</span><span class="n">pending</span><span class="p">(</span><span class="mf">0</span><span class="p">)</span>
<span class="mf">12</span><span class="p">:</span><span class="mf">30</span><span class="p">:</span><span class="mf">21</span><span class="p">:</span><span class="w"> </span><span class="n">asr</span><span class="o">=</span><span class="kr">run</span><span class="n">ning</span><span class="p">(</span><span class="mf">0</span><span class="p">)</span><span class="w"> </span><span class="n">cut</span><span class="o">=</span><span class="n">completed</span><span class="p">(</span><span class="mf">8951</span><span class="p">)</span><span class="w"> </span><span class="n">yolo</span><span class="o">=</span><span class="kr">run</span><span class="n">ning</span><span class="p">(</span><span class="mf">0</span><span class="p">)</span><span class="w"> </span><span class="n">face</span><span class="o">=</span><span class="n">pending</span><span class="p">(</span><span class="mf">0</span><span class="p">)</span><span class="w"> </span><span class="nb">pos</span><span class="n">e</span><span class="o">=</span><span class="n">pending</span><span class="p">(</span><span class="mf">0</span><span class="p">)</span><span class="w"> </span><span class="n">ocr</span><span class="o">=</span><span class="n">pending</span><span class="p">(</span><span class="mf">0</span><span class="p">)</span>
<span class="mf">12</span><span class="p">:</span><span class="mf">30</span><span class="p">:</span><span class="mf">31</span><span class="p">:</span><span class="w"> </span><span class="n">asr</span><span class="o">=</span><span class="kr">run</span><span class="n">ning</span><span class="p">(</span><span class="mf">0</span><span class="p">)</span><span class="w"> </span><span class="n">cut</span><span class="o">=</span><span class="n">completed</span><span class="p">(</span><span class="mf">8951</span><span class="p">)</span><span class="w"> </span><span class="n">yolo</span><span class="o">=</span><span class="n">completed</span><span class="p">(</span><span class="mf">8951</span><span class="p">)</span><span class="w"> </span><span class="n">face</span><span class="o">=</span><span class="kr">run</span><span class="n">ning</span><span class="p">(</span><span class="mf">0</span><span class="p">)</span><span class="w"> </span><span class="nb">pos</span><span class="n">e</span><span class="o">=</span><span class="n">pending</span><span class="p">(</span><span class="mf">0</span><span class="p">)</span>
<span class="mf">12</span><span class="p">:</span><span class="mf">30</span><span class="p">:</span><span class="mf">41</span><span class="p">:</span><span class="w"> </span><span class="n">asr</span><span class="o">=</span><span class="kr">run</span><span class="n">ning</span><span class="p">(</span><span class="mf">0</span><span class="p">)</span><span class="w"> </span><span class="n">cut</span><span class="o">=</span><span class="n">completed</span><span class="p">(</span><span class="mf">8951</span><span class="p">)</span><span class="w"> </span><span class="n">yolo</span><span class="o">=</span><span class="n">completed</span><span class="p">(</span><span class="mf">8951</span><span class="p">)</span><span class="w"> </span><span class="n">face</span><span class="o">=</span><span class="n">completed</span><span class="p">(</span><span class="mf">8951</span><span class="p">)</span><span class="w"> </span><span class="nb">pos</span><span class="n">e</span><span class="o">=</span><span class="kr">run</span><span class="n">ning</span><span class="p">(</span><span class="mf">0</span><span class="p">)</span>
<span class="mf">12</span><span class="p">:</span><span class="mf">30</span><span class="p">:</span><span class="mf">51</span><span class="p">:</span><span class="w"> </span><span class="n">asr</span><span class="o">=</span><span class="n">completed</span><span class="p">(</span><span class="mf">8951</span><span class="p">)</span><span class="w"> </span><span class="n">cut</span><span class="o">=</span><span class="n">completed</span><span class="p">(</span><span class="mf">8951</span><span class="p">)</span><span class="w"> </span><span class="n">yolo</span><span class="o">=</span><span class="n">completed</span><span class="p">(</span><span class="mf">8951</span><span class="p">)</span><span class="w"> </span><span class="n">face</span><span class="o">=</span><span class="n">completed</span><span class="p">(</span><span class="mf">8951</span><span class="p">)</span><span class="w"> </span><span class="nb">pos</span><span class="n">e</span><span class="o">=</span><span class="n">completed</span><span class="p">(</span><span class="mf">8951</span><span class="p">)</span><span class="w"> </span><span class="n">ocr</span><span class="o">=</span><span class="kr">run</span><span class="n">ning</span><span class="p">(</span><span class="mf">0</span><span class="p">)</span>
<span class="mf">12</span><span class="p">:</span><span class="mf">31</span><span class="p">:</span><span class="mf">01</span><span class="p">:</span><span class="w"> </span><span class="n">asr</span><span class="o">=</span><span class="n">completed</span><span class="p">(</span><span class="mf">8951</span><span class="p">)</span><span class="w"> </span><span class="n">cut</span><span class="o">=</span><span class="n">completed</span><span class="p">(</span><span class="mf">8951</span><span class="p">)</span><span class="w"> </span><span class="n">yolo</span><span class="o">=</span><span class="n">completed</span><span class="p">(</span><span class="mf">8951</span><span class="p">)</span><span class="w"> </span><span class="n">face</span><span class="o">=</span><span class="n">completed</span><span class="p">(</span><span class="mf">8951</span><span class="p">)</span><span class="w"> </span><span class="nb">pos</span><span class="n">e</span><span class="o">=</span><span class="n">completed</span><span class="p">(</span><span class="mf">8951</span><span class="p">)</span><span class="w"> </span><span class="n">ocr</span><span class="o">=</span><span class="n">completed</span><span class="p">(</span><span class="mf">8951</span><span class="p">)</span>
</code></pre></div>
<p><strong>Status transition chain</strong>: <code>pending → running → completed</code></p>
<p>Check job state:</p>
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-sf<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;X-API-Key: </span><span class="nv">$KEY</span><span class="s2">&quot;</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/jobs?uuid=</span><span class="si">${</span><span class="nv">UUID</span><span class="si">}</span><span class="s2">&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>jq<span class="w"> </span><span class="s1">&#39;[.jobs[]? | {id, status}]&#39;</span>
</code></pre></div>
<hr />
<h2>Step 7: Verify Results</h2>
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-sf<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;X-API-Key: </span><span class="nv">$KEY</span><span class="s2">&quot;</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/progress/</span><span class="si">${</span><span class="nv">UUID</span><span class="si">}</span><span class="s2">&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>jq<span class="w"> </span><span class="s1">&#39;{processors: [.processors[] | {name, status, frames: .frames_processed}]}&#39;</span>
</code></pre></div>
<p>Response:</p>
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;processors&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>
<span class="w"> </span><span class="p">{</span><span class="nt">&quot;name&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;asr&quot;</span><span class="p">,</span><span class="w"> </span><span class="nt">&quot;status&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;completed&quot;</span><span class="p">,</span><span class="w"> </span><span class="nt">&quot;frames&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">162568</span><span class="p">},</span>
<span class="w"> </span><span class="p">{</span><span class="nt">&quot;name&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;cut&quot;</span><span class="p">,</span><span class="w"> </span><span class="nt">&quot;status&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;completed&quot;</span><span class="p">,</span><span class="w"> </span><span class="nt">&quot;frames&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">162568</span><span class="p">},</span>
<span class="w"> </span><span class="p">{</span><span class="nt">&quot;name&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;yolo&quot;</span><span class="p">,</span><span class="w"> </span><span class="nt">&quot;status&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;completed&quot;</span><span class="p">,</span><span class="w"> </span><span class="nt">&quot;frames&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">162568</span><span class="p">},</span>
<span class="w"> </span><span class="p">{</span><span class="nt">&quot;name&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;face&quot;</span><span class="p">,</span><span class="w"> </span><span class="nt">&quot;status&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;completed&quot;</span><span class="p">,</span><span class="w"> </span><span class="nt">&quot;frames&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">162568</span><span class="p">},</span>
<span class="w"> </span><span class="p">{</span><span class="nt">&quot;name&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;pose&quot;</span><span class="p">,</span><span class="w"> </span><span class="nt">&quot;status&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;completed&quot;</span><span class="p">,</span><span class="w"> </span><span class="nt">&quot;frames&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">162568</span><span class="p">},</span>
<span class="w"> </span><span class="p">{</span><span class="nt">&quot;name&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;ocr&quot;</span><span class="p">,</span><span class="w"> </span><span class="nt">&quot;status&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;completed&quot;</span><span class="p">,</span><span class="w"> </span><span class="nt">&quot;frames&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">162568</span><span class="p">}</span>
<span class="w"> </span><span class="p">]</span>
<span class="p">}</span>
</code></pre></div>
<hr />
<h2>Step 8: Universal Search</h2>
<div class="codehilite"><pre><span></span><code><span class="c1"># Search for a person name</span>
curl<span class="w"> </span>-sf<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;X-API-Key: </span><span class="nv">$KEY</span><span class="s2">&quot;</span><span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;Content-Type: application/json&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>-d<span class="w"> </span><span class="s2">&quot;{\&quot;query\&quot;:\&quot;Audrey\&quot;,\&quot;uuid\&quot;:\&quot;</span><span class="si">${</span><span class="nv">UUID</span><span class="si">}</span><span class="s2">\&quot;,\&quot;limit\&quot;:3}&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/search/universal&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>jq<span class="w"> </span><span class="s1">&#39;{total, hits: [.results[]? | {chunk_id: .chunk_id[0:40], text: .text[0:80], score}]}&#39;</span>
</code></pre></div>
<p>Response:</p>
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;total&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;hits&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;chunk_id&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;3abeee81d94597629ed8cb943f182e94_998192&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;text&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Shorede stars two legends of classical Hollywood, Audrey Hepburn and Carrie Gran&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;score&quot;</span><span class="p">:</span><span class="w"> </span><span class="mf">0.9</span>
<span class="w"> </span><span class="p">},</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;chunk_id&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;3abeee81d94597629ed8cb943f182e94_998193&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;text&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Shorede stars two legends of classical Hollywood, Audrey Hepburn and Carrie Gran&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;score&quot;</span><span class="p">:</span><span class="w"> </span><span class="mf">0.9</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="p">]</span>
<span class="p">}</span>
</code></pre></div>
<div class="codehilite"><pre><span></span><code><span class="c1"># Search Chinese text</span>
curl<span class="w"> </span>-sf<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;X-API-Key: </span><span class="nv">$KEY</span><span class="s2">&quot;</span><span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;Content-Type: application/json&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>-d<span class="w"> </span><span class="s2">&quot;{\&quot;query\&quot;:\&quot;導演\&quot;,\&quot;uuid\&quot;:\&quot;</span><span class="si">${</span><span class="nv">UUID</span><span class="si">}</span><span class="s2">\&quot;,\&quot;limit\&quot;:3}&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/search/universal&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span><span class="s1">&#39;{total}&#39;</span>
</code></pre></div>
<p><strong>Search modes</strong>: The universal search endpoint supports:
- Text match (ILIKE on <code>text_content</code> and <code>content</code> columns)
- Time range filtering (<code>time_range: [start, end]</code>)
- Speaker/person ID filtering
- Chunk type filtering
- Visual content filtering (objects, density, classes)</p>
<hr />
<h2>Step 9: Get Chunk Detail</h2>
<div class="codehilite"><pre><span></span><code><span class="nv">CHUNK_ID</span><span class="o">=</span><span class="s2">&quot;3abeee81d94597629ed8cb943f182e94_998192&quot;</span>
curl<span class="w"> </span>-sf<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;X-API-Key: </span><span class="nv">$KEY</span><span class="s2">&quot;</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/file/</span><span class="si">${</span><span class="nv">UUID</span><span class="si">}</span><span class="s2">/chunk/</span><span class="si">${</span><span class="nv">CHUNK_ID</span><span class="si">}</span><span class="s2">&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>jq<span class="w"> </span><span class="s1">&#39;{chunk_id, chunk_type, text: .text_content, fps, start_frame, end_frame}&#39;</span>
</code></pre></div>
<p>Response:</p>
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;chunk_id&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;3abeee81d94597629ed8cb943f182e94_998192&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;chunk_type&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;sentence&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;text&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Shorede stars two legends of classical Hollywood, Audrey Hepburn and Carrie Gran&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;fps&quot;</span><span class="p">:</span><span class="w"> </span><span class="mf">23.976</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;start_frame&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">2395281</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;end_frame&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">2395341</span>
<span class="p">}</span>
</code></pre></div>
<hr />
<h2>Step 10: Chunk Fallback (Stale Qdrant Compatibility)</h2>
<p>Old integer-format chunk_ids from stale Qdrant payloads are automatically resolved via <code>WHERE id = int(chunk_id)</code>:</p>
<div class="codehilite"><pre><span></span><code><span class="c1"># Integer format (old Qdrant payload)</span>
curl<span class="w"> </span>-sf<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;X-API-Key: </span><span class="nv">$KEY</span><span class="s2">&quot;</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/file/</span><span class="si">${</span><span class="nv">UUID</span><span class="si">}</span><span class="s2">/chunk/998192&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>jq<span class="w"> </span><span class="s1">&#39;{chunk_id, text: .text_content}&#39;</span>
</code></pre></div>
<p>Response (same chunk as above):</p>
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;chunk_id&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;3abeee81d94597629ed8cb943f182e94_998192&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;text&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Shorede stars two legends of classical Hollywood, Audrey Hepburn and Carrie Gran&quot;</span>
<span class="p">}</span>
</code></pre></div>
<p><strong>Both formats work:</strong>
- <code>chunk/{uuid}_{id}</code> → exact <code>chunk_id</code> match
- <code>chunk/{id}</code> → fallback by primary key <code>id</code></p>
<hr />
<h2>Step 11: File Detail</h2>
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-sf<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;X-API-Key: </span><span class="nv">$KEY</span><span class="s2">&quot;</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/file/</span><span class="si">${</span><span class="nv">UUID</span><span class="si">}</span><span class="s2">&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>jq<span class="w"> </span><span class="s1">&#39;{file_name, status, file_type, file_path}&#39;</span>
</code></pre></div>
<p>Response:</p>
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;file_name&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Charade (1963) Cary Grant &amp; Audrey Hepburn ...mp4&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;status&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;completed&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;file_type&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;video&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;file_path&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;/Users/accusys/momentry/var/sftpgo/data/demo/Charade...&quot;</span>
<span class="p">}</span>
</code></pre></div>
<hr />
<h2>Step 12: File Identities</h2>
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-sf<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;X-API-Key: </span><span class="nv">$KEY</span><span class="s2">&quot;</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/file/</span><span class="si">${</span><span class="nv">UUID</span><span class="si">}</span><span class="s2">/identities&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>jq<span class="w"> </span><span class="s1">&#39;{total, identities: [.data[]? | {name, face_count, confidence}]}&#39;</span>
</code></pre></div>
<p>Response:</p>
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;total&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;identities&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>
<span class="w"> </span><span class="p">{</span><span class="nt">&quot;name&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Audrey Hepburn&quot;</span><span class="p">,</span><span class="w"> </span><span class="nt">&quot;face_count&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">22082</span><span class="p">,</span><span class="w"> </span><span class="nt">&quot;confidence&quot;</span><span class="p">:</span><span class="w"> </span><span class="mf">0.93</span><span class="p">},</span>
<span class="w"> </span><span class="p">{</span><span class="nt">&quot;name&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Cary Grant&quot;</span><span class="p">,</span><span class="w"> </span><span class="nt">&quot;face_count&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">15334</span><span class="p">,</span><span class="w"> </span><span class="nt">&quot;confidence&quot;</span><span class="p">:</span><span class="w"> </span><span class="mf">0.91</span><span class="p">}</span>
<span class="w"> </span><span class="p">]</span>
<span class="p">}</span>
</code></pre></div>
<hr />
<h2>Step 13: Identity Detail</h2>
<div class="codehilite"><pre><span></span><code><span class="c1"># List all global identities</span>
curl<span class="w"> </span>-sf<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;X-API-Key: </span><span class="nv">$KEY</span><span class="s2">&quot;</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/identities?page=1&amp;page_size=3&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>jq<span class="w"> </span><span class="s1">&#39;{total, identities: [.data[]? | {name, type: .identity_type, source}]}&#39;</span>
</code></pre></div>
<div class="codehilite"><pre><span></span><code><span class="c1"># Get identity files (cross-file faces)</span>
<span class="nv">IDENTITY_UUID</span><span class="o">=</span><span class="s2">&quot;c3545906-c82d-4b66-aa1d-150bc02decce&quot;</span>
curl<span class="w"> </span>-sf<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;X-API-Key: </span><span class="nv">$KEY</span><span class="s2">&quot;</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/identity/</span><span class="si">${</span><span class="nv">IDENTITY_UUID</span><span class="si">}</span><span class="s2">/files&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>jq<span class="w"> </span><span class="s1">&#39;{total, files: [.data[]? | {file_uuid: .file_uuid[0:16], face_count}]}&#39;</span>
</code></pre></div>
<hr />
<h2>Step 14: Schema &amp; Integrity Verification</h2>
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-sf<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/health/detailed&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span><span class="s1">&#39;{</span>
<span class="s1"> ip, port,</span>
<span class="s1"> schema: .schema.ok,</span>
<span class="s1"> migrations: [.schema.applied[]?.filename],</span>
<span class="s1"> integrity: .pipeline.scripts_integrity</span>
<span class="s1">}&#39;</span>
</code></pre></div>
<p>Response:</p>
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;ip&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;192.168.110.201&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;port&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">3002</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;schema&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;migrations&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>
<span class="w"> </span><span class="s2">&quot;migrate_add_content_hash.sql&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="s2">&quot;migrate_add_registered_status.sql&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="s2">&quot;migrate_add_schema_version.sql&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="s2">&quot;migrate_cleanup_inactive_identities.sql&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="s2">&quot;migrate_public_schema_v4_tables.sql&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="s2">&quot;migrate_public_schema_v4.sql&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="s2">&quot;migrate_public_v4_complete.sql&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="s2">&quot;migrate_fix_chunk_id_format.sql&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="s2">&quot;migrate_add_identity_indexes.sql&quot;</span>
<span class="w"> </span><span class="p">],</span>
<span class="w"> </span><span class="nt">&quot;integrity&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nt">&quot;matched&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">345</span><span class="p">,</span><span class="w"> </span><span class="nt">&quot;total&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">345</span><span class="p">,</span><span class="w"> </span><span class="nt">&quot;ok&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<hr />
<h2>Full Automation Script</h2>
<div class="codehilite"><pre><span></span><code><span class="ch">#!/bin/bash</span>
<span class="nb">set</span><span class="w"> </span>-euo<span class="w"> </span>pipefail
<span class="nv">API</span><span class="o">=</span><span class="s2">&quot;</span><span class="si">${</span><span class="nv">API</span><span class="k">:-</span><span class="nv">https</span><span class="p">://m5api.momentry.ddns.net</span><span class="si">}</span><span class="s2">&quot;</span>
<span class="nv">KEY</span><span class="o">=</span><span class="s2">&quot;</span><span class="si">${</span><span class="nv">KEY</span><span class="k">:-</span><span class="nv">muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69</span><span class="si">}</span><span class="s2">&quot;</span>
<span class="c1"># 1. Health</span>
<span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;=== Health ===&quot;</span>
curl<span class="w"> </span>-sf<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/health&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span><span class="s1">&#39;{status, version, build_git_hash}&#39;</span>
<span class="c1"># 2. Register file (argument: file path)</span>
<span class="nv">FILE_PATH</span><span class="o">=</span><span class="s2">&quot;</span><span class="si">${</span><span class="nv">1</span><span class="p">:?Usage: </span><span class="nv">$0</span><span class="p"> &lt;file_path&gt;</span><span class="si">}</span><span class="s2">&quot;</span>
<span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;=== Register ===&quot;</span>
<span class="nv">REG</span><span class="o">=</span><span class="k">$(</span>curl<span class="w"> </span>-sf<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;X-API-Key: </span><span class="nv">$KEY</span><span class="s2">&quot;</span><span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;Content-Type: application/json&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>-d<span class="w"> </span><span class="s2">&quot;{\&quot;file_path\&quot;:\&quot;</span><span class="nv">$FILE_PATH</span><span class="s2">\&quot;}&quot;</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/files/register&quot;</span><span class="k">)</span>
<span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$REG</span><span class="s2">&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span><span class="s1">&#39;{success, file_uuid, file_name}&#39;</span>
<span class="nv">UUID</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$REG</span><span class="s2">&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span>-r<span class="w"> </span><span class="s1">&#39;.file_uuid&#39;</span><span class="k">)</span>
<span class="o">[</span><span class="w"> </span>-z<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$UUID</span><span class="s2">&quot;</span><span class="w"> </span><span class="o">]</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="o">{</span><span class="w"> </span><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;Registration failed&quot;</span><span class="p">;</span><span class="w"> </span><span class="nb">exit</span><span class="w"> </span><span class="m">1</span><span class="p">;</span><span class="w"> </span><span class="o">}</span>
<span class="c1"># 3. Probe</span>
<span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;=== Probe ===&quot;</span>
curl<span class="w"> </span>-sf<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;X-API-Key: </span><span class="nv">$KEY</span><span class="s2">&quot;</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/file/</span><span class="si">${</span><span class="nv">UUID</span><span class="si">}</span><span class="s2">/probe&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>jq<span class="w"> </span><span class="s1">&#39;{name, fps, duration}&#39;</span>
<span class="c1"># 4. Submit job</span>
<span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;=== Process ===&quot;</span>
curl<span class="w"> </span>-sf<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;X-API-Key: </span><span class="nv">$KEY</span><span class="s2">&quot;</span><span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;Content-Type: application/json&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>-d<span class="w"> </span><span class="s1">&#39;{}&#39;</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/file/</span><span class="si">${</span><span class="nv">UUID</span><span class="si">}</span><span class="s2">/process&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span><span class="s1">&#39;{job_id, status}&#39;</span>
<span class="c1"># 5. Poll progress</span>
<span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;=== Waiting for pipeline... ===&quot;</span>
<span class="k">while</span><span class="w"> </span>true<span class="p">;</span><span class="w"> </span><span class="k">do</span>
<span class="w"> </span><span class="nv">PROGRESS</span><span class="o">=</span><span class="k">$(</span>curl<span class="w"> </span>-sf<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;X-API-Key: </span><span class="nv">$KEY</span><span class="s2">&quot;</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/progress/</span><span class="si">${</span><span class="nv">UUID</span><span class="si">}</span><span class="s2">&quot;</span><span class="k">)</span>
<span class="w"> </span><span class="nv">STATUS</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$PROGRESS</span><span class="s2">&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span>-r<span class="w"> </span><span class="s1">&#39;.status // &quot;?&quot;&#39;</span><span class="k">)</span>
<span class="w"> </span><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;</span><span class="k">$(</span>date<span class="w"> </span>+%H:%M:%S<span class="k">)</span><span class="s2">: </span><span class="k">$(</span><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$PROGRESS</span><span class="s2">&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span>-r<span class="w"> </span><span class="s1">&#39;[.processors[]? | &quot;\(.name)=\(.status)(\(.frames_processed))&quot;] | join(&quot; &quot;)&#39;</span><span class="k">)</span><span class="s2">&quot;</span>
<span class="w"> </span><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$PROGRESS</span><span class="s2">&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span>-e<span class="w"> </span><span class="s1">&#39;[.processors[]? | select(.status == &quot;pending&quot;)] | length == 0&#39;</span><span class="w"> </span>&gt;/dev/null<span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="k">break</span>
<span class="w"> </span>sleep<span class="w"> </span><span class="m">10</span>
<span class="k">done</span>
<span class="c1"># 6. Search</span>
<span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;=== Search ===&quot;</span>
curl<span class="w"> </span>-sf<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;X-API-Key: </span><span class="nv">$KEY</span><span class="s2">&quot;</span><span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;Content-Type: application/json&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>-d<span class="w"> </span><span class="s2">&quot;{\&quot;query\&quot;:\&quot;test\&quot;,\&quot;uuid\&quot;:\&quot;</span><span class="si">${</span><span class="nv">UUID</span><span class="si">}</span><span class="s2">\&quot;,\&quot;limit\&quot;:3}&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/search/universal&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span><span class="s1">&#39;{total, hits: [.results[]? | {chunk_id: .chunk_id[0:30], text: .text[0:60]}]}&#39;</span>
<span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;&quot;</span>
<span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;✅ Done: </span><span class="nv">$UUID</span><span class="s2">&quot;</span>
</code></pre></div>
</div>
</body>
</html>

View File

@@ -0,0 +1,923 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Tmdb User Guide - Momentry API Docs</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; padding: 40px; }
.container { max-width: 960px; margin: 0 auto; background: white; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); padding: 40px; }
h1 { font-size: 24px; margin: 24px 0 12px; }
h2 { font-size: 20px; margin: 20px 0 10px; color: #222; }
h3 { font-size: 16px; margin: 16px 0 8px; color: #444; }
p { line-height: 1.6; margin: 8px 0; }
table { border-collapse: collapse; width: 100%; margin: 12px 0; font-size: 14px; }
th, td { border: 1px solid #ddd; padding: 8px 12px; text-align: left; }
th { background: #f0f0f0; font-weight: 600; }
code { background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-size: 13px; }
pre { background: #f8f8f8; border: 1px solid #ddd; border-radius: 6px; padding: 12px; overflow-x: auto; margin: 12px 0; }
pre code { background: none; padding: 0; }
a { color: #0066cc; }
.back { display: inline-block; margin-bottom: 20px; color: #666; }
.back:hover { color: #333; }
</style>
</head>
<body>
<div class="container">
<a class="back" href="index.html">&larr; Back to index</a>
<hr />
<p>document_type: "user_manual"
service: "MOMENTRY_CORE"
title: "TMDb Enrichment 使用指南"
date: "2026-05-17"
version: "V1.0"
status: "active"
owner: "M5"
created_by: "OpenCode"</p>
<hr />
<h1>TMDb Enrichment 使用指南</h1>
<table class="table">
<thead>
<tr>
<th>項目</th>
<th>內容</th>
</tr>
</thead>
<tbody>
<tr>
<td>目標讀者</td>
<td>developer</td>
</tr>
<tr>
<td>預備知識</td>
<td>需有 API Key</td>
</tr>
</tbody>
</table>
<hr />
<h2>Base URL</h2>
<table class="table">
<thead>
<tr>
<th>Environment</th>
<th>URL</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td>Playground (Dev)</td>
<td><code>http://localhost:3003</code></td>
<td>Development and testing</td>
</tr>
<tr>
<td>Production</td>
<td><code>http://localhost:3002</code></td>
<td>Production deployment</td>
</tr>
<tr>
<td>External (M5)</td>
<td><code>https://m5api.momentry.ddns.net</code></td>
<td>Remote access</td>
</tr>
</tbody>
</table>
<h2>Variables</h2>
<p>All examples in this documentation use these environment variables:</p>
<div class="codehilite"><pre><span></span><code><span class="nv">API</span><span class="o">=</span><span class="s2">&quot;http://localhost:3003&quot;</span>
<span class="nv">KEY</span><span class="o">=</span><span class="s2">&quot;your-api-key-here&quot;</span>
</code></pre></div>
<h2>Authentication</h2>
<p>All endpoints under <code>/api/v1/*</code> require authentication.
The following endpoints are public (no auth needed):</p>
<ul>
<li><code>GET /health</code></li>
<li><code>POST /api/v1/auth/login</code></li>
<li><code>POST /api/v1/auth/logout</code></li>
</ul>
<h3>Three Authentication Modes</h3>
<p>The system supports three authentication methods, checked in <strong>priority order</strong> by the middleware:</p>
<div class="codehilite"><pre><span></span><code>Middleware priority:
1. Session Cookie (Portal/browser)
2. JWT Bearer (API clients: n8n, CLI)
3. API Key Header (legacy compatibility)
4. API Key Query Param (?api_key=)
</code></pre></div>
<table class="table">
<thead>
<tr>
<th>Mode</th>
<th>Transport</th>
<th>Expiry</th>
<th>Scope</th>
<th>Best for</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Session Cookie</strong></td>
<td><code>Cookie: session_id=&lt;uuid&gt;</code></td>
<td>24h</td>
<td>per-browser session</td>
<td>Portal (browser)</td>
</tr>
<tr>
<td><strong>JWT</strong></td>
<td><code>Authorization: Bearer &lt;token&gt;</code></td>
<td>1h</td>
<td>per-login token</td>
<td>API clients (n8n, CLI, scripts)</td>
</tr>
<tr>
<td><strong>API Key</strong></td>
<td><code>X-API-Key: &lt;key&gt;</code></td>
<td>90d</td>
<td>fixed key for automation</td>
<td>Legacy scripts, WordPress</td>
</tr>
</tbody>
</table>
<hr />
<h3>Login</h3>
<p><strong>Default accounts &amp; API keys:</strong></p>
<table class="table">
<thead>
<tr>
<th>Username</th>
<th>Password</th>
<th>API Key</th>
<th>Role</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>admin</code></td>
<td><code>admin</code></td>
<td></td>
<td>admin</td>
</tr>
<tr>
<td><code>demo</code></td>
<td><code>demo</code></td>
<td><code>muser_demo_key_32chars_abcdef1234567890</code></td>
<td>user</td>
</tr>
</tbody>
</table>
<p>The demo API key is set via <code>MOMENTRY_DEMO_API_KEY</code> env var and can be used in place of JWT for marcom integrations:</p>
<div class="codehilite"><pre><span></span><code><span class="c1"># Using API key instead of JWT</span>
curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/files/scan&quot;</span><span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;X-API-Key: muser_demo_key_32chars_abcdef1234567890&quot;</span>
</code></pre></div>
<div class="codehilite"><pre><span></span><code><span class="c1"># Login as admin</span>
curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/auth/login&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;Content-Type: application/json&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>-d<span class="w"> </span><span class="s1">&#39;{&quot;username&quot;: &quot;admin&quot;, &quot;password&quot;: &quot;admin&quot;}&#39;</span>
<span class="c1"># Login as demo user</span>
curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/auth/login&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;Content-Type: application/json&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>-d<span class="w"> </span><span class="s1">&#39;{&quot;username&quot;: &quot;demo&quot;, &quot;password&quot;: &quot;demo&quot;}&#39;</span>
</code></pre></div>
<h4>Success Response</h4>
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;success&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;jwt&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;eyJhbGciOiJIUzI1NiIs...&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;api_key&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;muser_...&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;user&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;username&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;admin&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;role&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;admin&quot;</span>
<span class="w"> </span><span class="p">},</span>
<span class="w"> </span><span class="nt">&quot;expires_at&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;2026-05-18T13:00:00Z&quot;</span>
<span class="p">}</span>
</code></pre></div>
<table class="table">
<thead>
<tr>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>jwt</code></td>
<td>string</td>
<td>JWT access token. Use as <code>Authorization: Bearer &lt;jwt&gt;</code>. Expires in 1 hour.</td>
</tr>
<tr>
<td><code>api_key</code></td>
<td>string</td>
<td>Legacy API key. Use as <code>X-API-Key: &lt;key&gt;</code>. Good for 90 days.</td>
</tr>
<tr>
<td><code>user.username</code></td>
<td>string</td>
<td>Username</td>
</tr>
<tr>
<td><code>user.role</code></td>
<td>string</td>
<td>Role: <code>admin</code>, <code>user</code>, or <code>readonly</code></td>
</tr>
<tr>
<td><code>expires_at</code></td>
<td>string</td>
<td>ISO8601 timestamp of JWT expiration</td>
</tr>
</tbody>
</table>
<p>The login endpoint also sets a <code>Set-Cookie</code> header for browser-based clients:</p>
<div class="codehilite"><pre><span></span><code><span class="nt">Set-Cookie</span><span class="o">:</span><span class="w"> </span><span class="nt">session_id</span><span class="o">=&lt;</span><span class="nt">uuid</span><span class="o">&gt;;</span><span class="w"> </span><span class="nt">Path</span><span class="o">=/</span><span class="nt">api</span><span class="o">;</span><span class="w"> </span><span class="nt">HttpOnly</span><span class="o">;</span><span class="w"> </span><span class="nt">SameSite</span><span class="o">=</span><span class="nt">Strict</span><span class="o">;</span><span class="w"> </span><span class="nt">Max-Age</span><span class="o">=</span><span class="nt">86400</span>
</code></pre></div>
<h4>Error Response (401)</h4>
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;success&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;message&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Invalid username or password&quot;</span>
<span class="p">}</span>
</code></pre></div>
<hr />
<h3>Using JWT</h3>
<p>JWT is preferred for API clients (n8n, CLI scripts, WordPress). It is validated by the middleware without a database lookup (stateless).</p>
<div class="codehilite"><pre><span></span><code><span class="c1"># Login and capture JWT</span>
<span class="nv">JWT</span><span class="o">=</span><span class="k">$(</span>curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/auth/login&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;Content-Type: application/json&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>-d<span class="w"> </span><span class="s1">&#39;{&quot;username&quot;:&quot;admin&quot;,&quot;password&quot;:&quot;admin&quot;}&#39;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>python3<span class="w"> </span>-c<span class="w"> </span><span class="s2">&quot;import json,sys;print(json.load(sys.stdin)[&#39;jwt&#39;])&quot;</span><span class="k">)</span>
<span class="c1"># Use JWT for all subsequent requests</span>
curl<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;Authorization: Bearer </span><span class="nv">$JWT</span><span class="s2">&quot;</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/files/scan&quot;</span>
curl<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;Authorization: Bearer </span><span class="nv">$JWT</span><span class="s2">&quot;</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/resource/tmdb&quot;</span>
</code></pre></div>
<p>JWT is short-lived (1 hour). When it expires, request a new one via login.</p>
<hr />
<h3>Using Session Cookie (Browser)</h3>
<p>Browser-based clients (Portal) get a session cookie automatically after login. The browser sends the cookie with every request—no manual header needed.</p>
<div class="codehilite"><pre><span></span><code><span class="c1"># Login captures the session cookie from Set-Cookie header</span>
curl<span class="w"> </span>-v<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/auth/login&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;Content-Type: application/json&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>-d<span class="w"> </span><span class="s1">&#39;{&quot;username&quot;:&quot;admin&quot;,&quot;password&quot;:&quot;admin&quot;}&#39;</span><span class="w"> </span><span class="m">2</span>&gt;<span class="p">&amp;</span><span class="m">1</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span><span class="s2">&quot;Set-Cookie&quot;</span>
<span class="c1"># Browser automatically sends: Cookie: session_id=&lt;uuid&gt;</span>
<span class="c1"># No manual header needed for subsequent requests</span>
</code></pre></div>
<p>The session cookie is HttpOnly (not accessible from JavaScript) and SameSite=Strict (protected against CSRF).</p>
<hr />
<h3>Using Legacy API Key</h3>
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;X-API-Key: </span><span class="nv">$KEY</span><span class="s2">&quot;</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/files/scan&quot;</span>
<span class="c1"># Also accepted via Bearer header (non-JWT format) or query parameter:</span>
curl<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;Authorization: Bearer </span><span class="nv">$KEY</span><span class="s2">&quot;</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/files/scan&quot;</span>
curl<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/files/scan?api_key=</span><span class="nv">$KEY</span><span class="s2">&quot;</span>
</code></pre></div>
<p>API keys are validated via SHA256 hash lookup in the database. They are long-lived (90 days) and intended for automation.</p>
<h3>Obtaining an API Key (CLI)</h3>
<div class="codehilite"><pre><span></span><code>momentry<span class="w"> </span>api-key<span class="w"> </span>create<span class="w"> </span><span class="s2">&quot;My API Key&quot;</span><span class="w"> </span>--key-type<span class="w"> </span>user
</code></pre></div>
<hr />
<h3>Logout</h3>
<div class="codehilite"><pre><span></span><code><span class="c1"># Logout using the session cookie (browser)</span>
curl<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/auth/logout&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;Cookie: session_id=&lt;uuid&gt;&quot;</span>
</code></pre></div>
<h4>What logout does</h4>
<table class="table">
<thead>
<tr>
<th>Auth mode</th>
<th>Effect</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Session Cookie</strong></td>
<td>Session deleted from database. Same cookie returns 401 on subsequent requests.</td>
</tr>
<tr>
<td><strong>JWT</strong></td>
<td>JWT remains valid until expiry. (JWT is stateless — logout adds JWT to a blacklist only if API key mode is used.)</td>
</tr>
<tr>
<td><strong>API Key</strong></td>
<td>API key remains valid. (Legacy keys are shared across sessions — revoking would break other clients.)</td>
</tr>
</tbody>
</table>
<h4>Example: full session lifecycle</h4>
<div class="codehilite"><pre><span></span><code><span class="c1"># 1. Login</span>
<span class="nv">SESSION_ID</span><span class="o">=</span><span class="k">$(</span>curl<span class="w"> </span>-s<span class="w"> </span>-D<span class="w"> </span>-<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/auth/login&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;Content-Type: application/json&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>-d<span class="w"> </span><span class="s1">&#39;{&quot;username&quot;:&quot;admin&quot;,&quot;password&quot;:&quot;admin&quot;}&#39;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span><span class="s2">&quot;Set-Cookie&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>sed<span class="w"> </span><span class="s1">&#39;s/.*session_id=\([^;]*\).*/\1/&#39;</span><span class="k">)</span>
<span class="c1"># 2. Use session (works)</span>
curl<span class="w"> </span>-s<span class="w"> </span>-o<span class="w"> </span>/dev/null<span class="w"> </span>-w<span class="w"> </span><span class="s2">&quot;HTTP %{http_code}\n&quot;</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/resource/tmdb&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;Cookie: session_id=</span><span class="nv">$SESSION_ID</span><span class="s2">&quot;</span>
<span class="c1"># → HTTP 200</span>
<span class="c1"># 3. Logout</span>
curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/auth/logout&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;Cookie: session_id=</span><span class="nv">$SESSION_ID</span><span class="s2">&quot;</span>
<span class="c1"># → {&quot;success&quot;: true}</span>
<span class="c1"># 4. Use session again (rejected)</span>
curl<span class="w"> </span>-s<span class="w"> </span>-o<span class="w"> </span>/dev/null<span class="w"> </span>-w<span class="w"> </span><span class="s2">&quot;HTTP %{http_code}\n&quot;</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/resource/tmdb&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;Cookie: session_id=</span><span class="nv">$SESSION_ID</span><span class="s2">&quot;</span>
<span class="c1"># → HTTP 401</span>
</code></pre></div>
<hr />
<h3>Authentication Flow Summary</h3>
<div class="codehilite"><pre><span></span><code>Login Request
┌──────────────────┐
│ 1. Check users │ ← users table (argon2 password verify)
│ table │
└──────┬───────────┘
┌───┴───┐
│ match │
└───┬───┘
┌──────────────────┐
│ 2. Create JWT │ ← 1h expiry, signed with JWT_SECRET
├──────────────────┤
│ 3. Create │ ← 24h expiry, stored in sessions table
│ session │
├──────────────────┤
│ 4. Set-Cookie │ ← HttpOnly, SameSite=Strict, Path=/api
├──────────────────┤
│ 5. Return │ ← JWT + api_key + user info to client
└──────────────────┘
</code></pre></div>
<div class="codehilite"><pre><span></span><code>Protected Request
┌──────────────────────┐
│ Middleware checks: │
│ │
│ 1. Cookie session? │ → DB lookup session → get api_key → verify
│ │
│ 2. JWT Bearer? │ → verify JWT signature → decode claims
│ │
│ 3. X-API-Key? │ → SHA256 hash → DB lookup → verify
│ │
│ 4. ?api_key=? │ → same as #3
│ │
│ 5. None → 401 │
└──────────────────────┘
</code></pre></div>
<hr />
<h3>Error Responses</h3>
<table class="table">
<thead>
<tr>
<th>HTTP</th>
<th>When</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>401</code></td>
<td>Missing or invalid authentication</td>
</tr>
<tr>
<td><code>401</code></td>
<td>Session expired or logged out</td>
</tr>
<tr>
<td><code>401</code></td>
<td>JWT expired</td>
</tr>
<tr>
<td><code>401</code></td>
<td>API key revoked or inactive</td>
</tr>
</tbody>
</table>
<hr />
<h3>Related</h3>
<ul>
<li><code>POST /api/v1/resource/tmdb/check</code> — test authentication + TMDb API connectivity</li>
<li><code>GET /health/detailed</code> — view auth status (integrations section)</li>
</ul>
<hr />
<h2>File Registration</h2>
<h3><code>POST /api/v1/files/register</code></h3>
<p><strong>Auth</strong>: Required
<strong>Scope</strong>: file-level</p>
<p>Register a video file for processing. Returns the file's metadata and UUID.</p>
<p><strong>New in v0.1.2</strong>: Registration now <strong>automatically triggers the processing pipeline</strong> — no need to call <code>POST /api/v1/file/:uuid/process</code> separately. The system will:
1. Register the file and run ffprobe
2. Auto-run offline TMDb probe (reads local identity files, no API calls)
3. Create a monitor job for the worker
4. Worker starts all 10 processors (Cut → ASR → ASRX → YOLO → OCR → Face → Pose → VisualChunk → Story → 5W1H)</p>
<p>If the file already exists (same content hash), returns the existing record with <code>already_exists: true</code>.</p>
<h4>Request Parameters</h4>
<table class="table">
<thead>
<tr>
<th>Field</th>
<th>Type</th>
<th>Required</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>file_path</code></td>
<td>string</td>
<td>Yes</td>
<td></td>
<td>Path to video file on disk</td>
</tr>
<tr>
<td><code>pattern</code></td>
<td>string</td>
<td>No</td>
<td></td>
<td>Regex pattern for batch register (requires <code>file_path</code> to be a directory)</td>
</tr>
<tr>
<td><code>user_id</code></td>
<td>integer</td>
<td>No</td>
<td></td>
<td>User ID to associate with registration</td>
</tr>
<tr>
<td><code>content_hash</code></td>
<td>string</td>
<td>No</td>
<td></td>
<td>Pre-computed SHA-256 hash (skips computation)</td>
</tr>
</tbody>
</table>
<h4>Example</h4>
<div class="codehilite"><pre><span></span><code><span class="c1"># Register a single file</span>
curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/files/register&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;Content-Type: application/json&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;X-API-Key: </span><span class="nv">$KEY</span><span class="s2">&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>-d<span class="w"> </span><span class="s1">&#39;{&quot;file_path&quot;: &quot;/path/to/video.mp4&quot;}&#39;</span>
<span class="c1"># Batch register files matching a pattern in a directory</span>
curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/files/register&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;Content-Type: application/json&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;X-API-Key: </span><span class="nv">$KEY</span><span class="s2">&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>-d<span class="w"> </span><span class="s1">&#39;{&quot;file_path&quot;: &quot;/path/to/dir&quot;, &quot;pattern&quot;: &quot;.*\\.mp4$&quot;}&#39;</span>
</code></pre></div>
<h4>Response (200)</h4>
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;success&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;file_uuid&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;3a6c1865...&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;file_name&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;video.mp4&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;file_path&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;/path/to/video.mp4&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;file_type&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;video&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;duration&quot;</span><span class="p">:</span><span class="w"> </span><span class="mf">120.5</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;width&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">1920</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;height&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">1080</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;fps&quot;</span><span class="p">:</span><span class="w"> </span><span class="mf">24.0</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;total_frames&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">2892</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;already_exists&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;message&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;File registered successfully&quot;</span>
<span class="p">}</span>
</code></pre></div>
<table class="table">
<thead>
<tr>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>success</code></td>
<td>boolean</td>
<td>Always true on 200</td>
</tr>
<tr>
<td><code>file_uuid</code></td>
<td>string</td>
<td>32-char hex UUID of the registered file</td>
</tr>
<tr>
<td><code>file_name</code></td>
<td>string</td>
<td>File name (auto-renamed if name conflict)</td>
</tr>
<tr>
<td><code>file_path</code></td>
<td>string</td>
<td>Canonical path on disk</td>
</tr>
<tr>
<td><code>file_type</code></td>
<td>string</td>
<td><code>"video"</code>, <code>"audio"</code>, or <code>"unknown"</code></td>
</tr>
<tr>
<td><code>duration</code></td>
<td>float</td>
<td>Duration in seconds</td>
</tr>
<tr>
<td><code>width</code></td>
<td>integer</td>
<td>Video width in pixels</td>
</tr>
<tr>
<td><code>height</code></td>
<td>integer</td>
<td>Video height in pixels</td>
</tr>
<tr>
<td><code>fps</code></td>
<td>float</td>
<td>Frames per second</td>
</tr>
<tr>
<td><code>total_frames</code></td>
<td>integer</td>
<td>Total frame count</td>
</tr>
<tr>
<td><code>already_exists</code></td>
<td>boolean</td>
<td>True if same content was already registered</td>
</tr>
<tr>
<td><code>message</code></td>
<td>string</td>
<td>Human-readable status</td>
</tr>
</tbody>
</table>
<h4>Error Responses</h4>
<table class="table">
<thead>
<tr>
<th>HTTP</th>
<th>When</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>401</code></td>
<td>Missing or invalid API key</td>
</tr>
<tr>
<td><code>400</code></td>
<td>Invalid request body</td>
</tr>
<tr>
<td><code>404</code></td>
<td>File path does not exist</td>
</tr>
</tbody>
</table>
<hr />
<h3><code>GET /api/v1/files/scan</code></h3>
<p><strong>Auth</strong>: Required
<strong>Scope</strong>: file-level</p>
<p>Scan the filesystem directory and list all media files, showing which are registered, processing, or unregistered.</p>
<h4>Query Parameters</h4>
<table class="table">
<thead>
<tr>
<th>Field</th>
<th>Type</th>
<th>Required</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>page</code></td>
<td>integer</td>
<td>No</td>
<td>1</td>
<td>Page number (1-based)</td>
</tr>
<tr>
<td><code>page_size</code></td>
<td>integer</td>
<td>No</td>
<td>all</td>
<td>Items per page (alias: <code>limit</code>)</td>
</tr>
<tr>
<td><code>limit</code></td>
<td>integer</td>
<td>No</td>
<td>all</td>
<td>Max items (alias for <code>page_size</code>)</td>
</tr>
<tr>
<td><code>pattern</code></td>
<td>string</td>
<td>No</td>
<td></td>
<td>Regex filter on file name (e.g., <code>.*\\.mp4$</code>)</td>
</tr>
<tr>
<td><code>sort_by</code></td>
<td>string</td>
<td>No</td>
<td><code>name</code></td>
<td>Sort field: <code>name</code>, <code>size</code>, <code>modified</code>, <code>status</code></td>
</tr>
<tr>
<td><code>sort_order</code></td>
<td>string</td>
<td>No</td>
<td><code>asc</code></td>
<td>Sort direction: <code>asc</code> or <code>desc</code></td>
</tr>
</tbody>
</table>
<h4>Example</h4>
<div class="codehilite"><pre><span></span><code><span class="c1"># Full scan</span>
curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/files/scan&quot;</span><span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;X-API-Key: </span><span class="nv">$KEY</span><span class="s2">&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span><span class="s1">&#39;{total, registered_count, unregistered_count}&#39;</span>
<span class="c1"># Paginated (page 1, 5 per page)</span>
curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/files/scan?page=1&amp;page_size=5&quot;</span><span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;X-API-Key: </span><span class="nv">$KEY</span><span class="s2">&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span><span class="s1">&#39;{page, total_pages, files: [.files[].file_name]}&#39;</span>
<span class="c1"># Regex filter: only mp4 files</span>
curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/files/scan?pattern=.*\\.mp4</span>$<span class="s2">&quot;</span><span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;X-API-Key: </span><span class="nv">$KEY</span><span class="s2">&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span><span class="s1">&#39;{filtered_total, files: [.files[].file_name]}&#39;</span>
<span class="c1"># Sort by file size (largest first)</span>
curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/files/scan?sort_by=size&amp;sort_order=desc&amp;page_size=5&quot;</span><span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;X-API-Key: </span><span class="nv">$KEY</span><span class="s2">&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span><span class="s1">&#39;[.files[] | {file_name, file_size}]&#39;</span>
<span class="c1"># Sort by modified time (most recent first)</span>
curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/files/scan?sort_by=modified&amp;sort_order=desc&amp;page_size=5&quot;</span><span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;X-API-Key: </span><span class="nv">$KEY</span><span class="s2">&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span><span class="s1">&#39;[.files[] | {file_name, modified_time}]&#39;</span>
<span class="c1"># Sort by status</span>
curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/files/scan?sort_by=status&amp;page_size=5&quot;</span><span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;X-API-Key: </span><span class="nv">$KEY</span><span class="s2">&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span><span class="s1">&#39;[.files[] | {file_name, status}]&#39;</span>
</code></pre></div>
<h4>Response (200)</h4>
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;files&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;file_name&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;video.mp4&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;file_size&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">12345678</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;is_registered&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;file_uuid&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;3a6c1865...&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;status&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;completed&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;registration_time&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;2026-05-16T12:00:00Z&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;job_id&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">42</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="p">],</span>
<span class="w"> </span><span class="nt">&quot;total&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">107</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;filtered_total&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">80</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;page&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;page_size&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">20</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;total_pages&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;registered_count&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">26</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;unregistered_count&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">81</span>
<span class="p">}</span>
</code></pre></div>
<table class="table">
<thead>
<tr>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>files</code></td>
<td>array</td>
<td>Array of file info objects (paginated)</td>
</tr>
<tr>
<td><code>files[].file_name</code></td>
<td>string</td>
<td>File name</td>
</tr>
<tr>
<td><code>files[].relative_path</code></td>
<td>string</td>
<td>Path relative to scan root</td>
</tr>
<tr>
<td><code>files[].file_path</code></td>
<td>string</td>
<td>Absolute path on disk</td>
</tr>
<tr>
<td><code>files[].file_size</code></td>
<td>integer</td>
<td>File size in bytes</td>
</tr>
<tr>
<td><code>files[].modified_time</code></td>
<td>string</td>
<td>Last modified timestamp (ISO8601)</td>
</tr>
<tr>
<td><code>files[].is_registered</code></td>
<td>boolean</td>
<td>Whether file is registered in DB</td>
</tr>
<tr>
<td><code>files[].file_uuid</code></td>
<td>string</td>
<td>32-char hex UUID (only if registered)</td>
</tr>
<tr>
<td><code>files[].status</code></td>
<td>string</td>
<td><code>"completed"</code>, <code>"processing"</code>, <code>"registered"</code>, <code>"unregistered"</code>, or <code>null</code></td>
</tr>
<tr>
<td><code>files[].registration_time</code></td>
<td>string</td>
<td>DB registration timestamp (only if registered)</td>
</tr>
<tr>
<td><code>files[].job_id</code></td>
<td>integer</td>
<td>Processing job ID (only if a job exists)</td>
</tr>
<tr>
<td><code>total</code></td>
<td>integer</td>
<td>Total files found on disk (unfiltered)</td>
</tr>
<tr>
<td><code>filtered_total</code></td>
<td>integer</td>
<td>Files matching regex filter</td>
</tr>
<tr>
<td><code>page</code></td>
<td>integer</td>
<td>Current page number</td>
</tr>
<tr>
<td><code>page_size</code></td>
<td>integer</td>
<td>Items per page</td>
</tr>
<tr>
<td><code>total_pages</code></td>
<td>integer</td>
<td>Total pages</td>
</tr>
<tr>
<td><code>registered_count</code></td>
<td>integer</td>
<td>Files registered in DB</td>
</tr>
<tr>
<td><code>unregistered_count</code></td>
<td>integer</td>
<td>Files not yet registered</td>
</tr>
</tbody>
</table>
<h4>Notes</h4>
<table class="table">
<thead>
<tr>
<th>Feature</th>
<th>Behavior</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Regex</strong></td>
<td>Case-insensitive (<code>(?i)</code> prefix auto-applied). Applied to <code>file_name</code>.</td>
</tr>
<tr>
<td><strong>Sort order</strong></td>
<td>Default (<code>sort_by=name</code>): registered files first, then alphabetically. <code>sort_by=status</code>: alphabetical by status string.</td>
</tr>
<tr>
<td><strong>Pagination</strong></td>
<td><code>page_size</code> and <code>limit</code> are aliases. Default: show all results.</td>
</tr>
<tr>
<td><strong>Processing order</strong></td>
<td><code>pattern</code> regex filter → <code>sort_by</code>/<code>sort_order</code><code>page</code>/<code>page_size</code> slice.</td>
</tr>
</tbody>
</table>
<hr />
<h2>TMDb Enrichment</h2>
<blockquote>
<p>⚠️ <strong>External resource</strong>: TMDb requires internet access, violating Momentry's local-only principle.
All core processing (ASR, YOLO, Face, OCR, Pose, embeddings) runs fully offline.
TMDb enrichment is <strong>optional</strong> and gated behind <code>TMDB_API_KEY</code> + <code>MOMENTRY_TMDB_PROBE_ENABLED</code>.</p>
</blockquote>
<h3>Overview</h3>
<p>TMDb enrichment is an optional identity enrichment step that can be run after Pipeline face detection completes. The workflow is:</p>
<ol>
<li><strong>Prefetch</strong> (requires internet): Download movie cast data from TMDb API → cache to <code>{file_uuid}.tmdb.json</code></li>
<li><strong>Probe</strong>: Read local cache → create identities for <strong>all</strong> cast members (<code>source='tmdb'</code>) + save <code>identity.json</code> + download profile image to <code>{OUTPUT}/identities/{uuid}/profile.jpg</code></li>
<li><strong>Match</strong>: The worker automatically matches video faces against TMDb identities when <code>MOMENTRY_TMDB_PROBE_ENABLED=true</code></li>
</ol>
<h3><code>POST /api/v1/agents/tmdb/prefetch</code></h3>
<p><strong>Auth</strong>: Required
<strong>Scope</strong>: file-level</p>
<p>Fetch TMDb cast data for a registered file and cache it locally. This is the only step requiring internet access.</p>
<h4>Request Parameters</h4>
<table class="table">
<thead>
<tr>
<th>Field</th>
<th>Type</th>
<th>Required</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>file_uuid</code></td>
<td>string</td>
<td>Yes</td>
<td>File UUID to enrich</td>
</tr>
</tbody>
</table>
<h4>Example</h4>
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/agents/tmdb/prefetch&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;Content-Type: application/json&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;X-API-Key: </span><span class="nv">$KEY</span><span class="s2">&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>-d<span class="w"> </span><span class="s1">&#39;{&quot;file_uuid&quot;: &quot;&#39;</span><span class="s2">&quot;</span><span class="nv">$FILE_UUID</span><span class="s2">&quot;</span><span class="s1">&#39;&quot;}&#39;</span>
</code></pre></div>
<h4>Response (200)</h4>
<div class="codehilite"><pre><span></span><code><span class="p">{</span><span class="nt">&quot;success&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"> </span><span class="nt">&quot;file_uuid&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;...&quot;</span><span class="p">,</span><span class="w"> </span><span class="nt">&quot;cache_path&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;/output/...tmdb.json&quot;</span><span class="p">}</span>
</code></pre></div>
<h3><code>POST /api/v1/file/:file_uuid/tmdb-probe</code></h3>
<p><strong>Auth</strong>: Required
<strong>Scope</strong>: file-level</p>
<p>Read local TMDb cache and create/update identities. Requires prefetch to have been run first.</p>
<h4>Example</h4>
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/file/</span><span class="nv">$FILE_UUID</span><span class="s2">/tmdb-probe&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;X-API-Key: </span><span class="nv">$KEY</span><span class="s2">&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span><span class="s1">&#39;{identities_created, movie_title}&#39;</span>
</code></pre></div>
<h4>Response (200 — identities created)</h4>
<div class="codehilite"><pre><span></span><code><span class="p">{</span><span class="nt">&quot;success&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"> </span><span class="nt">&quot;identities_created&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">15</span><span class="p">,</span><span class="w"> </span><span class="nt">&quot;movie_title&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Charade&quot;</span><span class="p">}</span>
</code></pre></div>
<h4>Response (200 — no cache)</h4>
<div class="codehilite"><pre><span></span><code><span class="p">{</span><span class="nt">&quot;success&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w"> </span><span class="nt">&quot;message&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;No TMDb cache found. Run tmdb-prefetch first.&quot;</span><span class="p">}</span>
</code></pre></div>
<h3><code>GET /api/v1/resource/tmdb</code></h3>
<p><strong>Auth</strong>: Required
<strong>Scope</strong>: system-level</p>
<p>View TMDb resource status including configuration, identity counts, and cache file count.</p>
<h4>Example</h4>
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/resource/tmdb&quot;</span><span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;X-API-Key: </span><span class="nv">$KEY</span><span class="s2">&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span><span class="s1">&#39;{identities_seeded, cache_files}&#39;</span>
</code></pre></div>
<h3><code>POST /api/v1/resource/tmdb/check</code></h3>
<p><strong>Auth</strong>: Required
<strong>Scope</strong>: system-level</p>
<p>Ping the TMDb API to verify connectivity and measure latency.</p>
<h4>Example</h4>
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/resource/tmdb/check&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;X-API-Key: </span><span class="nv">$KEY</span><span class="s2">&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span><span class="s1">&#39;.status&#39;</span>
</code></pre></div>
<h4>Response</h4>
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;api_key_configured&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;enabled&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;api_reachable&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;api_latency_ms&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">120</span>
<span class="p">}</span>
</code></pre></div>
<hr />
</div>
</body>
</html>

View File

@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Momentry API Docs</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; padding: 40px; }
.container { max-width: 900px; margin: 0 auto; background: white; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); padding: 40px; }
h1 { font-size: 28px; margin-bottom: 8px; }
p.subtitle { color: #666; margin-bottom: 24px; }
ul { list-style: none; }
li { padding: 8px 0; border-bottom: 1px solid #eee; }
li:last-child { border: none; }
a { color: #0066cc; text-decoration: none; font-size: 16px; }
a:hover { text-decoration: underline; }
</style>
</head>
<body>
<div class="container">
<h1>Momentry API Documentation</h1>
<p class="subtitle">Generated from API_WORKSPACE modules</p>
<ul><li><a href="API_ACCESS.html">Api Access</a></li><li><a href="API_ENDPOINTS.html">Api Endpoints</a></li><li><a href="API_ERROR_CODES.html">Api Error Codes</a></li><li><a href="API_INDEX.html">Api Index</a></li><li><a href="API_QUICK_REFERENCE.html">Api Quick Reference</a></li><li><a href="API_REFERENCE.html">Api Reference</a></li><li><a href="API_TRAINING_MARCOM.html">Api Training Marcom</a></li><li><a href="Demo_EndToEnd.html">Demo Endtoend</a></li><li><a href="M5API_Pipeline_Demo.html">M5Api Pipeline Demo</a></li><li><a href="TMDb_User_Guide.html">Tmdb User Guide</a></li></ul>
</div>
</body>
</html>

View File

@@ -0,0 +1,46 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login - Momentry Docs</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; display: flex; justify-content: center; align-items: center; height: 100vh; }
.card { background: white; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); padding: 40px; width: 360px; }
h1 { font-size: 24px; margin-bottom: 24px; text-align: center; }
input { width: 100%; padding: 10px 12px; margin-bottom: 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px; }
button { width: 100%; padding: 10px; background: #0066cc; color: white; border: none; border-radius: 6px; font-size: 16px; cursor: pointer; }
button:hover { background: #0052a3; }
.error { color: #cc0000; font-size: 13px; margin-bottom: 12px; display: none; }
</style>
</head>
<body>
<div class="card">
<h1>Momentry Docs</h1>
<form id="loginForm">
<input type="text" id="username" placeholder="Username" value="demo" required>
<input type="password" id="password" placeholder="Password" value="demo" required>
<div class="error" id="error">Invalid credentials</div>
<button type="submit">Login</button>
</form>
</div>
<script>
document.getElementById('loginForm').onsubmit = async function(e) {
e.preventDefault();
const resp = await fetch('/api/v1/auth/login', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
username: document.getElementById('username').value,
password: document.getElementById('password').value
})
});
if (resp.ok) {
window.location.href = '/doc/index.html';
} else {
document.getElementById('error').style.display = 'block';
}
};
</script>
</body>
</html>