Files
momentry_core/docs_v1.0/doc/07_identity.html
Accusys 14e886cc08 feat: progressive multi-round face matching + pending person API
- Identity agent: per-face max matching, multi-round with derived
  seeds from high-confidence faces, angle diversity filter (cosine sim < 0.90)
- Pending person API: POST /file/:file_uuid/pending-person
  + GET /file/:file_uuid/pending-persons with status=pending, source=manual
- Update API docs (07_identity.md)
2026-06-24 03:42:04 +08:00

1744 lines
87 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>07 Identity - 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; }
.topbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
.logout-btn { font-size: 13px; color: #999; text-decoration: none; }
.logout-btn:hover { color: #cc0000; }
</style>
</head>
<body>
<div class="container">
<div class="topbar">
<a class="back" href="index.html">&larr; Back to index</a>
<a class="logout-btn" href="#" onclick="fetch('/api/v1/auth/logout',{method:'POST'}).then(()=>window.location.reload());return false">Logout</a>
</div>
<!-- module: identity -->
<!-- description: Global identities — CRUD, detail, files, faces, bind, unbind, search -->
<!-- depends: 01_auth -->
<h2>Global Identities</h2>
<h3><code>GET /api/v1/identities</code></h3>
<p><strong>Auth</strong>: Required
<strong>Scope</strong>: identity-level</p>
<p>List all registered identities with pagination.</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/identities?page=1&amp;page_size=20&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;{count, identities: [.identities[] | {name}]}&#39;</span>
</code></pre></div>
<hr />
<h3><code>GET /api/v1/identity/:identity_uuid</code></h3>
<p><strong>Auth</strong>: Required
<strong>Scope</strong>: identity-level</p>
<p>Get detailed information for a specific identity, including metadata and TMDb references.</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/identity/</span><span class="nv">$IDENTITY_UUID</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>
</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;identity_uuid&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;a9a901056d6b46ff92da0c3c1a57dff4&quot;</span><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;Cary Grant&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;identity_type&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;people&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;source&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;tmdb&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;confirmed&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;tmdb_id&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">112</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;tmdb_profile&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;{output}/identities/{identity_uuid}/profile.jpg&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;metadata&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{},</span>
<span class="w"> </span><span class="nt">&quot;reference_data&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{},</span>
<span class="w"> </span><span class="nt">&quot;created_at&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;updated_at&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</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>identity_uuid</code></td>
<td>string</td>
<td>Identity identifier</td>
</tr>
<tr>
<td><code>name</code></td>
<td>string</td>
<td>Identity name</td>
</tr>
<tr>
<td><code>identity_type</code></td>
<td>string</td>
<td><code>"people"</code> or null</td>
</tr>
<tr>
<td><code>source</code></td>
<td>string</td>
<td><code>.json</code>, <code>auto</code>, <code>tmdb</code>, <code>user_defined</code>, or <code>merged</code></td>
</tr>
<tr>
<td><code>status</code></td>
<td>string</td>
<td><code>"confirmed"</code>, <code>"pending"</code>, or <code>"inactive"</code></td>
</tr>
<tr>
<td><code>tmdb_id</code></td>
<td>integer</td>
<td>TMDb person ID (only if source = tmdb)</td>
</tr>
<tr>
<td><code>tmdb_profile</code></td>
<td>string</td>
<td>Local profile image path (<code>{output}/identities/{uuid}/profile.jpg</code>)</td>
</tr>
<tr>
<td><code>metadata</code></td>
<td>object</td>
<td>Metadata JSON (tmdb_character, cast_order, etc.)</td>
</tr>
<tr>
<td><code>created_at</code></td>
<td>string</td>
<td>Creation timestamp</td>
</tr>
</tbody>
</table>
<hr />
<h3><code>DELETE /api/v1/identity/:identity_uuid</code></h3>
<p><strong>Auth</strong>: Required
<strong>Scope</strong>: identity-level</p>
<p>Delete an identity permanently. All face detections bound to this identity are unbound (<code>identity_id</code> set to <code>NULL</code>). The identity JSON file is deleted from disk.</p>
<h4>History &amp; Undo/Redo</h4>
<p>Every DELETE records a full snapshot of the identity and its unbound faces. See <a href="14_identity_history.md#4-delete-history--undoredo"><code>14_identity_history.md</code></a> for:</p>
<ul>
<li>Undo via <code>POST /api/v1/identity/:identity_uuid/undo</code> — recreates identity and re-binds faces</li>
<li>Redo via <code>POST /api/v1/identity/:identity_uuid/redo</code> — re-deletes the identity</li>
</ul>
<p><strong>Note</strong>: Delete undo/redo reuses the same endpoints as PATCH undo/redo. The endpoint automatically detects whether the identity was deleted (undo) or needs to be re-deleted (redo) based on the history record.</p>
<hr />
<h3><code>PATCH /api/v1/identity/:identity_uuid</code></h3>
<p><strong>Auth</strong>: Required
<strong>Scope</strong>: identity-level</p>
<p>Partially update an identity. Only provided fields are modified. The <code>name</code> field is a display label and may repeat across identities (removed UNIQUE constraint). Aliases for multilingual display are stored in <code>metadata.aliases</code> (see BCP 47 reference below).</p>
<h4>Request (JSON, all fields optional)</h4>
<table class="table">
<thead>
<tr>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>name</code></td>
<td>string</td>
<td>New display name</td>
</tr>
<tr>
<td><code>metadata</code></td>
<td>object</td>
<td>Merged into existing metadata. Use <code>"aliases"</code> key for locale-tagged names</td>
</tr>
<tr>
<td><code>status</code></td>
<td>string</td>
<td><code>"confirmed"</code>, <code>"pending"</code>, or <code>"skipped"</code></td>
</tr>
<tr>
<td><code>identity_type</code></td>
<td>string</td>
<td><code>"people"</code>, <code>"brand"</code>, <code>"object"</code>, <code>"concept"</code>, etc.</td>
</tr>
</tbody>
</table>
<h4>Example</h4>
<div class="codehilite"><pre><span></span><code><span class="c1"># Update name and add aliases</span>
curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>PATCH<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/identity/</span><span class="nv">$IDENTITY_UUID</span><span class="s2">&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>-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;{</span>
<span class="s1"> &quot;name&quot;: &quot;John Smith&quot;,</span>
<span class="s1"> &quot;metadata&quot;: {</span>
<span class="s1"> &quot;aliases&quot;: [</span>
<span class="s1"> {&quot;locale&quot;: &quot;en&quot;, &quot;name&quot;: &quot;John Smith&quot;},</span>
<span class="s1"> {&quot;locale&quot;: &quot;zh-TW&quot;, &quot;name&quot;: &quot;約翰·史密斯&quot;},</span>
<span class="s1"> {&quot;locale&quot;: &quot;ja&quot;, &quot;name&quot;: &quot;ジョン・スミス&quot;}</span>
<span class="s1"> ]</span>
<span class="s1"> }</span>
<span class="s1"> }&#39;</span>
<span class="c1"># Update status only</span>
curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>PATCH<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$API</span><span class="s2">/api/v1/identity/</span><span class="nv">$IDENTITY_UUID</span><span class="s2">&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>-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;status&quot;: &quot;confirmed&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;identity_uuid&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;a9a901056d6b46ff92da0c3c1a57dff4&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;updated_fields&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&quot;name&quot;</span><span class="p">,</span><span class="w"> </span><span class="s2">&quot;metadata&quot;</span><span class="p">]</span>
<span class="p">}</span>
</code></pre></div>
<h4>Error Responses</h4>
<table class="table">
<thead>
<tr>
<th>HTTP</th>
<th>When</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>404</code></td>
<td>Identity not found</td>
</tr>
<tr>
<td><code>500</code></td>
<td>Database error</td>
</tr>
</tbody>
</table>
<h4>History &amp; Undo/Redo</h4>
<p>Every bind records a before/after snapshot. See <a href="14_identity_history.md#2-bindunbindtrace-history--undoredo"><code>14_identity_history.md</code></a> for:</p>
<ul>
<li><code>POST /api/v1/identity/:identity_uuid/bind/undo</code> — Revert a bind</li>
<li><code>POST /api/v1/identity/:identity_uuid/bind/redo</code> — Reapply an undone bind</li>
<li><code>GET /api/v1/identity/:identity_uuid/bind/history</code> — Query bind operations</li>
</ul>
<hr />
<h2>Metadata (Embedded JSON)</h2>
<p>The <code>identities.metadata</code> column is a <strong>JSONB</strong> field that stores arbitrary structured data alongside the identity's core fields (name, status, identity_type). No schema is enforced — any valid JSON object is accepted.</p>
<h3>Merge Behavior</h3>
<table class="table">
<thead>
<tr>
<th>Operation</th>
<th>Strategy</th>
<th>Example</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>PATCH</strong></td>
<td>Shallow top-level merge: <code>COALESCE(metadata,'{}'::jsonb) \|\| $1::jsonb</code></td>
<td>Sending <code>{"tmdb_rating": 8.5}</code> only adds/overwrites <code>tmdb_rating</code>; all other existing keys are preserved.</td>
</tr>
<tr>
<td><strong>mergeinto</strong></td>
<td>Recursive deep merge — nested sub-keys are merged individually, not replaced wholesale</td>
<td>Target has <code>{"tmdb": {"biography": "..."}}</code>, source has <code>{"tmdb": {"birthday": "1904-01-18"}}</code> → result is <code>{"tmdb": {"biography": "...", "birthday": "1904-01-18"}}</code>.</td>
</tr>
<tr>
<td><strong>Upload (<code>POST</code>)</strong></td>
<td>Direct overwrite — the entire <code>metadata</code> field is replaced with the request value.</td>
<td></td>
</tr>
</tbody>
</table>
<h3>Validation</h3>
<table class="table">
<thead>
<tr>
<th>Scenario</th>
<th>Result</th>
</tr>
</thead>
<tbody>
<tr>
<td>PATCH with non-object metadata (<code>string</code>, <code>array</code>, <code>number</code>, <code>null</code>)</td>
<td><code>400 Bad Request: "metadata must be a JSON object"</code></td>
</tr>
<tr>
<td>mergeinto with non-object metadata</td>
<td>Accepted (mergeinto validates at application level)</td>
</tr>
<tr>
<td>Upload with non-object metadata</td>
<td>Accepted (upload replaces directly)</td>
</tr>
</tbody>
</table>
<h3>Conventional Keys</h3>
<table class="table">
<thead>
<tr>
<th>Key</th>
<th>Type</th>
<th>Writer</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>aliases</code></td>
<td><code>[{locale, name}]</code></td>
<td>PATCH, mergeinto</td>
<td>Multilingual display names (see <a href="#alias-system-bcp-47-locale-tags">Alias System</a>)</td>
</tr>
<tr>
<td><code>merged_into</code></td>
<td><code>{uuid, at}</code></td>
<td>mergeinto</td>
<td>Marks an identity as merged (undo mechanism reads this)</td>
</tr>
<tr>
<td><code>tmdb_*</code></td>
<td>various</td>
<td>TMDb probe</td>
<td>Movie metadata (biography, birthday, known_for, etc.). Written only when <code>MOMENTRY_TMDB_PROBE_ENABLED=true</code>.</td>
</tr>
<tr>
<td><code>source</code></td>
<td>string</td>
<td>mergeinto</td>
<td>Tagged on aliases/metadata when added by merge (<code>"merge"</code> value)</td>
</tr>
</tbody>
</table>
<p>Custom keys are fully supported — no registration required.</p>
<h3>Search Coverage</h3>
<p>The identity search endpoint (<code>GET /api/v1/identity/search</code>) matches across three scopes:</p>
<ol>
<li><code>i.name</code> — exact and ILIKE against display name</li>
<li><code>jsonb_array_elements(i.metadata-&gt;'aliases')-&gt;&gt;'name'</code> — locale-tagged alias names</li>
<li><code>i.metadata::text ILIKE $1</code> — raw string search across the entire JSON blob (all keys, all values)</li>
</ol>
<p>This means searching for <code>"1904-01-18"</code> or <code>"biography"</code> will match identities whose metadata contains those strings anywhere.</p>
<h3>History Snapshots</h3>
<p>Every <code>identity_history</code> record captures the <strong>full metadata</strong> in both <code>before_snapshot</code> and <code>after_snapshot</code> (as part of the complete identity JSONB dump). Undo restores the identity row — including metadata — to the <code>before_snapshot</code> state.</p>
<p>For merge operations, the MongoDB merge history records <code>metadata_fields_added</code> and <code>metadata_fields_added_paths</code> (dot-separated paths like <code>"tmdb.biography"</code>). Merge undo removes only those specific paths, preserving subsequent manual edits to other metadata keys.</p>
<h3>Best Practices</h3>
<table class="table">
<thead>
<tr>
<th>Guideline</th>
<th>Reason</th>
</tr>
</thead>
<tbody>
<tr>
<td>Deep nesting is allowed in metadata</td>
<td>All metadata merge operations use <code>jsonb_deep_merge()</code> — nested sub-keys are merged recursively, not replaced wholesale</td>
</tr>
<tr>
<td>Use <code>aliases</code> for display names</td>
<td>Frontend has built-in locale fallback logic (see <a href="#alias-system-bcp-47-locale-tags">Alias System</a>)</td>
</tr>
<tr>
<td>Avoid &gt;1MB per identity</td>
<td>Metadata is included in search indexing (<code>metadata::text ILIKE</code>); large blobs degrade query performance</td>
</tr>
<tr>
<td>Don't rely on metadata ordering</td>
<td>JSONB preserves insertion order but PostgreSQL does not guarantee it across operations</td>
</tr>
<tr>
<td>No LLM/Gemma4 agent writes to metadata</td>
<td>Only API endpoints (PATCH, mergeinto, upload) and TMDb probe modify <code>identities.metadata</code></td>
</tr>
</tbody>
</table>
<hr />
<h3><code>POST /api/v1/identity/:identity_uuid/bind/trace</code></h3>
<p><strong>Auth</strong>: Required
<strong>Scope</strong>: identity-level</p>
<p>Bind all face detections of a trace to an identity. Updates all rows in <code>face_detections</code> with the matching <code>file_uuid</code> and <code>trace_id</code>.</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 where trace exists</td>
</tr>
<tr>
<td><code>trace_id</code></td>
<td>integer</td>
<td>Yes</td>
<td>Trace ID (from <code>face_detections.trace_id</code>)</td>
</tr>
</tbody>
</table>
<h4>Side Effects</h4>
<ul>
<li>清除該 trace 所有 face detection rows 的 <code>stranger_id</code>(設為 NULL</li>
<li>不影響 <code>identities</code> 表中原有的 stranger auto-identity 記錄</li>
</ul>
<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/identity/</span><span class="nv">$IDENTITY_UUID</span><span class="s2">/bind/trace&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>-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_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;, &quot;trace_id&quot;: 919}&#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;message&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Bound trace 919 of aeed71342... to Cary Grant&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;data&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nt">&quot;rows_affected&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">53</span><span class="w"> </span><span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<h4>Error Responses</h4>
<table class="table">
<thead>
<tr>
<th>HTTP</th>
<th>When</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>404</code></td>
<td>Identity not found</td>
</tr>
<tr>
<td><code>500</code></td>
<td>Database error</td>
</tr>
</tbody>
</table>
<h4>History &amp; Undo/Redo</h4>
<p>Trace bind operations share the same history/undo/redo system as single-face binds. See <a href="14_identity_history.md#2-bindunbindtrace-history--undoredo"><code>14_identity_history.md</code></a> for endpoints.</p>
<hr />
<h3><code>GET /api/v1/identity/:identity_uuid/traces</code></h3>
<p><strong>Auth</strong>: Required
<strong>Scope</strong>: identity-level</p>
<p>Get paginated face traces (continuous tracking segments) associated with this identity across all files.</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><code>1</code></td>
<td>Page number</td>
</tr>
<tr>
<td><code>page_size</code></td>
<td>integer</td>
<td>No</td>
<td><code>20</code></td>
<td>Items per page</td>
</tr>
</tbody>
</table>
<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/identity/</span><span class="nv">$IDENTITY_UUID</span><span class="s2">/traces?page=1&amp;page_size=3&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;{total, total_faces, traces}&#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;identity_uuid&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;a9a901056d6b46ff92da0c3c1a57dff4&quot;</span><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;Cary Grant&quot;</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">18</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">3</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;total_faces&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">542</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;traces&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_uuid&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;aeed71342a899fe4b4c57b7d41bcb692&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;trace_id&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">906</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;frame_count&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">52</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;first_frame&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">37974</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;last_frame&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">38127</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;first_sec&quot;</span><span class="p">:</span><span class="w"> </span><span class="mf">1519.0</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;last_sec&quot;</span><span class="p">:</span><span class="w"> </span><span class="mf">1525.1</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;avg_confidence&quot;</span><span class="p">:</span><span class="w"> </span><span class="mf">0.8254</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="p">]</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>bool</td>
<td>Always <code>true</code></td>
</tr>
<tr>
<td><code>identity_uuid</code></td>
<td>string</td>
<td>Identity UUID</td>
</tr>
<tr>
<td><code>name</code></td>
<td>string</td>
<td>Identity display name</td>
</tr>
<tr>
<td><code>total</code></td>
<td>integer</td>
<td>Total number of traces (across all pages)</td>
</tr>
<tr>
<td><code>total_faces</code></td>
<td>integer</td>
<td>Sum of all face detections in returned traces</td>
</tr>
<tr>
<td><code>traces[].file_uuid</code></td>
<td>string</td>
<td>File where trace exists</td>
</tr>
<tr>
<td><code>traces[].trace_id</code></td>
<td>integer</td>
<td>Trace tracking ID</td>
</tr>
<tr>
<td><code>traces[].frame_count</code></td>
<td>integer</td>
<td>Number of frames in this trace</td>
</tr>
<tr>
<td><code>traces[].first_frame</code></td>
<td>integer</td>
<td>Start frame number</td>
</tr>
<tr>
<td><code>traces[].last_frame</code></td>
<td>integer</td>
<td>End frame number</td>
</tr>
<tr>
<td><code>traces[].first_sec</code></td>
<td>float</td>
<td>Start time in seconds</td>
</tr>
<tr>
<td><code>traces[].last_sec</code></td>
<td>float</td>
<td>End time in seconds</td>
</tr>
<tr>
<td><code>traces[].avg_confidence</code></td>
<td>float</td>
<td>Average detection confidence (0.01.0)</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>404</code></td>
<td>Identity not found</td>
</tr>
<tr>
<td><code>500</code></td>
<td>Database error</td>
</tr>
</tbody>
</table>
<hr />
<h3><code>POST /api/v1/identity/:identity_uuid/unbind</code></h3>
<p><strong>Auth</strong>: Required
<strong>Scope</strong>: identity-level</p>
<p>Unbind a face detection from an identity. Removes the identity association from the face record.</p>
<h4>Side Effects</h4>
<ul>
<li>只清除 <code>identity_id</code>(設為 NULL<strong>不會恢復 <code>stranger_id</code></strong></li>
<li>被 unbind 的 face 不會自動成為 stranger</li>
<li>要重新標記為 stranger 需重新跑 Agent API<code>identity/analyze</code></li>
</ul>
<h4>History &amp; Undo/Redo</h4>
<p>Unbind records a before/after snapshot. See <a href="14_identity_history.md#2-bindunbindtrace-history--undoredo"><code>14_identity_history.md</code></a> for:</p>
<ul>
<li><code>POST /api/v1/identity/:identity_uuid/bind/undo</code> — Revert an unbind</li>
<li><code>POST /api/v1/identity/:identity_uuid/bind/redo</code> — Reapply an undone unbind</li>
</ul>
<hr />
<h3><code>POST /api/v1/identity/:identity_uuid/mergeinto</code></h3>
<p><strong>Auth</strong>: Required
<strong>Scope</strong>: identity-level</p>
<p>Transfer all face bindings from this identity to another identity, then optionally delete or mark the source as merged.</p>
<h4>Two Merge Cases</h4>
<table class="table">
<thead>
<tr>
<th>Case</th>
<th>Description</th>
<th>Undo/Redo Support</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>stranger → identity</strong></td>
<td>Merge an auto-generated stranger identity into a known identity (TMDb or user-defined)</td>
<td>✅ 24hr undo/redo</td>
</tr>
<tr>
<td><strong>identity A → identity B</strong></td>
<td>Merge two known identities (e.g., duplicate entries)</td>
<td>✅ 24hr undo/redo</td>
</tr>
</tbody>
</table>
<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>into_uuid</code></td>
<td>string</td>
<td>Yes</td>
<td></td>
<td>Target identity UUID to merge into</td>
</tr>
<tr>
<td><code>keep_history</code></td>
<td>bool</td>
<td>No</td>
<td><code>true</code></td>
<td>Keep source identity record with <code>status='merged'</code> (<code>true</code>) or delete it (<code>false</code>)</td>
</tr>
</tbody>
</table>
<h4>Side Effects</h4>
<ul>
<li>轉移所有 <code>face_detections.identity_id</code> 到目標 identity</li>
<li>同時清除所有被轉移 rows 的 <code>stranger_id</code></li>
<li>將 source name 加入 target aliases (with <code>source: "merge"</code> tag)</li>
<li>將 source aliases 加入 target aliases (if not already present)</li>
<li>將 source metadata fields 加入 target metadata (if not already present)</li>
<li><code>keep_history: true</code>預設source identity 設為 <code>status='merged'</code>,保留記錄</li>
<li><code>keep_history: false</code><strong>刪除</strong> source identity 及其 identity JSON 檔案</li>
<li><strong>記錄 merge history 到 MongoDB</strong>(支援 undo/redo</li>
</ul>
<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/identity/</span><span class="nv">$SOURCE_UUID</span><span class="s2">/mergeinto&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>-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;into_uuid&quot;: &quot;&#39;</span><span class="s2">&quot;</span><span class="nv">$TARGET_UUID</span><span class="s2">&quot;</span><span class="s1">&#39;&quot;, &quot;keep_history&quot;: true}&#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;message&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Merged &#39;stranger_13894&#39; into &#39;Louis Viret&#39; (52 faces transferred, history kept)&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;data&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;merge_id&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;550e8400-e29b-41d4-a716-446655440000&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;faces_transferred&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">52</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;aliases_added&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;metadata_fields_added&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span>
<span class="w"> </span><span class="p">}</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>merge_id</code></td>
<td>string</td>
<td>Unique merge operation ID (for undo)</td>
</tr>
<tr>
<td><code>faces_transferred</code></td>
<td>integer</td>
<td>Number of face detections transferred</td>
</tr>
<tr>
<td><code>aliases_added</code></td>
<td>integer</td>
<td>Number of aliases added to target</td>
</tr>
<tr>
<td><code>metadata_fields_added</code></td>
<td>integer</td>
<td>Number of metadata fields added to target</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>404</code></td>
<td>Source or target identity not found</td>
</tr>
<tr>
<td><code>500</code></td>
<td>Database error</td>
</tr>
</tbody>
</table>
<hr />
<h3><code>POST /api/v1/identity/merge/:merge_id/undo</code></h3>
<p><strong>Auth</strong>: Required
<strong>Scope</strong>: identity-level</p>
<p>Undo a merge operation within 24 hours. Restores the source identity and reverts face bindings.</p>
<h4>Undo Behavior</h4>
<table class="table">
<thead>
<tr>
<th>Action</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>Restore source identity</td>
<td>If <code>keep_history=true</code>: restore status to <code>confirmed</code><br>If <code>keep_history=false</code>: recreate identity from MongoDB snapshot</td>
</tr>
<tr>
<td>Restore faces</td>
<td>Transfer faces back to source identity</td>
</tr>
<tr>
<td>Remove aliases from target</td>
<td>Remove aliases with <code>source: "merge"</code> tag</td>
</tr>
<tr>
<td>Remove metadata fields from target</td>
<td>Remove fields that were added from source</td>
</tr>
<tr>
<td><strong>Preserve manual changes</strong></td>
<td>Keep aliases/metadata manually added after merge</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/identity/merge/550e8400-e29b-41d4-a716-446655440000/undo&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>
</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;message&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Undo merge completed: &#39;stranger_13894&#39; restored, 52 faces reverted&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;data&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;source_identity_restored&quot;</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;a9a90105...&quot;</span><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;stranger_13894&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;confirmed&quot;</span>
<span class="w"> </span><span class="p">},</span>
<span class="w"> </span><span class="nt">&quot;faces_reverted&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">52</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;aliases_removed_from_target&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;metadata_fields_removed_from_target&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<h4>Error Responses</h4>
<table class="table">
<thead>
<tr>
<th>HTTP</th>
<th>When</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>400</code></td>
<td>Undo deadline expired (&gt;24hr) or already undone</td>
</tr>
<tr>
<td><code>404</code></td>
<td>Merge record not found</td>
</tr>
<tr>
<td><code>500</code></td>
<td>Database error</td>
</tr>
</tbody>
</table>
<hr />
<h3><code>POST /api/v1/identity/merge/:merge_id/redo</code></h3>
<p><strong>Auth</strong>: Required
<strong>Scope</strong>: identity-level</p>
<p>Redo a previously undone merge operation. See <a href="14_identity_history.md#post-apiv1identitymergemerge_idredo"><code>14_identity_history.md</code></a> for full details.</p>
<hr />
<h3><code>GET /api/v1/identity/merge/history</code></h3>
<p><strong>Auth</strong>: Required
<strong>Scope</strong>: identity-level</p>
<p>Query merge history records from MongoDB.</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>source_uuid</code></td>
<td>string</td>
<td>No</td>
<td></td>
<td>Filter by source identity UUID</td>
</tr>
<tr>
<td><code>target_uuid</code></td>
<td>string</td>
<td>No</td>
<td></td>
<td>Filter by target identity UUID</td>
</tr>
<tr>
<td><code>merge_id</code></td>
<td>string</td>
<td>No</td>
<td></td>
<td>Filter by specific merge ID</td>
</tr>
<tr>
<td><code>undone</code></td>
<td>bool</td>
<td>No</td>
<td></td>
<td>Filter by undone status</td>
</tr>
<tr>
<td><code>page</code></td>
<td>int</td>
<td>No</td>
<td>1</td>
<td>Page number</td>
</tr>
<tr>
<td><code>page_size</code></td>
<td>int</td>
<td>No</td>
<td>20</td>
<td>Items per page</td>
</tr>
</tbody>
</table>
<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/identity/merge/history?page=1&amp;page_size=10&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>
</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;total&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;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">10</span><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;merge_id&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;550e8400-e29b-41d4-a716-446655440000&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;source_name&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;stranger_13894&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;target_name&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Louis Viret&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;faces_transferred&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">52</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;merged_at&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;2026-05-27T10:00:00Z&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;undo_deadline&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;2026-05-28T10:00:00Z&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;undone&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;undo_expired&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="p">]</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>merge_id</code></td>
<td>string</td>
<td>Unique merge operation ID</td>
</tr>
<tr>
<td><code>source_name</code></td>
<td>string</td>
<td>Source identity name</td>
</tr>
<tr>
<td><code>target_name</code></td>
<td>string</td>
<td>Target identity name</td>
</tr>
<tr>
<td><code>faces_transferred</code></td>
<td>integer</td>
<td>Number of faces transferred</td>
</tr>
<tr>
<td><code>merged_at</code></td>
<td>datetime</td>
<td>When merge occurred</td>
</tr>
<tr>
<td><code>undo_deadline</code></td>
<td>datetime</td>
<td>24hr deadline for undo</td>
</tr>
<tr>
<td><code>undone</code></td>
<td>bool</td>
<td>Whether merge was undone</td>
</tr>
<tr>
<td><code>undo_expired</code></td>
<td>bool</td>
<td>Whether undo deadline passed</td>
</tr>
</tbody>
</table>
<hr />
<h3><code>GET /api/v1/identities/search</code></h3>
<p><strong>Auth</strong>: Required
<strong>Scope</strong>: global / file-level</p>
<p>Search identity name → find associated chunks. Searches identity name and aliases, returns identities with their associated text chunks.</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>q</code></td>
<td>string</td>
<td>Yes</td>
<td></td>
<td>Search text (ILIKE match on name and aliases)</td>
</tr>
<tr>
<td><code>file_uuid</code></td>
<td>string</td>
<td>No</td>
<td></td>
<td>Restrict to specific file. If omitted, searches all files (global search)</td>
</tr>
<tr>
<td><code>limit</code></td>
<td>integer</td>
<td>No</td>
<td>50</td>
<td>Max results</td>
</tr>
</tbody>
</table>
<h4>Example (Global Search)</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/identities/search?q=Audrey&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>
</code></pre></div>
<h4>Example (File-specific Search)</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/identities/search?q=Audrey&amp;file_uuid=</span><span class="nv">$FILE_UUID</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>
</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;total&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;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;identity_id&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">9</span><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;Audrey Hepburn&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;source&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;tmdb&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;tmdb_id&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">1932</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;a6fb22eebefaef17e62af874997c5944&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;trace_id&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">41</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;llm_parent_..._204_207&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">204.162</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;text_content&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;...confrontation...&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>
<table class="table">
<thead>
<tr>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>results[].identity_id</code></td>
<td>integer</td>
<td>Identity ID</td>
</tr>
<tr>
<td><code>results[].name</code></td>
<td>string</td>
<td>Identity name</td>
</tr>
<tr>
<td><code>results[].source</code></td>
<td>string</td>
<td>Identity source (<code>tmdb</code>, <code>user_defined</code>, etc.)</td>
</tr>
<tr>
<td><code>results[].tmdb_id</code></td>
<td>integer</td>
<td>TMDb person ID (if source = tmdb)</td>
</tr>
<tr>
<td><code>results[].file_uuid</code></td>
<td>string</td>
<td>File where identity appears</td>
</tr>
<tr>
<td><code>results[].trace_id</code></td>
<td>integer</td>
<td>Face trace ID</td>
</tr>
<tr>
<td><code>results[].chunk_id</code></td>
<td>string</td>
<td>Associated chunk ID</td>
</tr>
<tr>
<td><code>results[].start_time</code></td>
<td>float</td>
<td>Chunk start time</td>
</tr>
<tr>
<td><code>results[].text_content</code></td>
<td>string</td>
<td>Chunk text content</td>
</tr>
</tbody>
</table>
<hr />
<hr />
<h3><code>POST /api/v1/identity/upload</code></h3>
<p><strong>Auth</strong>: Required
<strong>Scope</strong>: identity-level</p>
<p>Upload an identity.json file to create or update an identity. Accepts the same format as the identity.json files stored on disk.</p>
<p>If an identity with the same <code>identity_uuid</code> already exists, it will be updated with the new values.</p>
<h4>Request</h4>
<p>The request body is an <code>IdentityFile</code> object:</p>
<table class="table">
<thead>
<tr>
<th>Field</th>
<th>Type</th>
<th>Required</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>identity_uuid</code></td>
<td>string</td>
<td>Yes</td>
<td>Identity identifier</td>
</tr>
<tr>
<td><code>name</code></td>
<td>string</td>
<td>Yes</td>
<td>Identity display name</td>
</tr>
<tr>
<td><code>identity_type</code></td>
<td>string</td>
<td>No</td>
<td><code>"people"</code> or null</td>
</tr>
<tr>
<td><code>source</code></td>
<td>string</td>
<td>No</td>
<td><code>.json</code>, <code>auto</code>, <code>tmdb</code>, <code>user_defined</code>, or <code>merged</code></td>
</tr>
<tr>
<td><code>status</code></td>
<td>string</td>
<td>No</td>
<td><code>"confirmed"</code>, <code>"pending"</code>, or <code>"inactive"</code></td>
</tr>
<tr>
<td><code>tmdb_id</code></td>
<td>integer</td>
<td>No</td>
<td>TMDb person ID</td>
</tr>
<tr>
<td><code>tmdb_profile</code></td>
<td>string</td>
<td>No</td>
<td>TMDb profile image URL</td>
</tr>
<tr>
<td><code>metadata</code></td>
<td>object</td>
<td>No</td>
<td>Arbitrary metadata JSON</td>
</tr>
<tr>
<td><code>file_bindings</code></td>
<td>array</td>
<td>No</td>
<td>Array of <code>{ file_uuid, trace_ids, face_count }</code> (informational)</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/identity/upload&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>-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;{</span>
<span class="s1"> &quot;version&quot;: 1,</span>
<span class="s1"> &quot;identity_uuid&quot;: &quot;a9a901056d6b46ff92da0c3c1a57dff4&quot;,</span>
<span class="s1"> &quot;name&quot;: &quot;Cary Grant&quot;,</span>
<span class="s1"> &quot;identity_type&quot;: &quot;people&quot;,</span>
<span class="s1"> &quot;source&quot;: &quot;.json&quot;,</span>
<span class="s1"> &quot;status&quot;: &quot;confirmed&quot;,</span>
<span class="s1"> &quot;metadata&quot;: {},</span>
<span class="s1"> &quot;file_bindings&quot;: []</span>
<span class="s1"> }&#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;identity_uuid&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;a9a901056d6b46ff92da0c3c1a57dff4&quot;</span><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;Cary Grant&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;Identity uploaded successfully&quot;</span>
<span class="p">}</span>
</code></pre></div>
<hr />
<hr />
<h3><code>POST /api/v1/identity/:identity_uuid/profile-image</code></h3>
<p><strong>Auth</strong>: Required
<strong>Scope</strong>: identity-level</p>
<p>Upload a profile image (JPEG or PNG) for an identity. The image is saved to <code>{output}/identities/{uuid}/profile.{ext}</code>.</p>
<p>Uses <code>multipart/form-data</code> with field name <code>image</code>.</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/identity/</span><span class="nv">$IDENTITY_UUID</span><span class="s2">/profile-image&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>-F<span class="w"> </span><span class="s2">&quot;image=@/path/to/photo.jpg&quot;</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;identity_uuid&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;a9a901056d6b46ff92da0c3c1a57dff4&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;path&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;/path/to/output/identities/.../profile.jpg&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;Profile image saved: profile.jpg&quot;</span>
<span class="p">}</span>
</code></pre></div>
<h4>Error Responses</h4>
<table class="table">
<thead>
<tr>
<th>HTTP</th>
<th>When</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>400</code></td>
<td>Missing image field or unsupported format</td>
</tr>
<tr>
<td><code>404</code></td>
<td>Identity not found</td>
</tr>
<tr>
<td><code>415</code></td>
<td>Unsupported image type (use JPEG or PNG)</td>
</tr>
</tbody>
</table>
<hr />
<h3><code>GET /api/v1/identity/:identity_uuid/profile-image</code></h3>
<p><strong>Auth</strong>: Required
<strong>Scope</strong>: identity-level</p>
<p>Retrieve the profile image for an identity. Returns the raw image data with appropriate Content-Type header.</p>
<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/identity/</span><span class="nv">$IDENTITY_UUID</span><span class="s2">/profile-image&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>-o<span class="w"> </span>profile.jpg
</code></pre></div>
<table class="table">
<thead>
<tr>
<th>Response Header</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>content-type</code></td>
<td><code>image/jpeg</code> or <code>image/png</code></td>
</tr>
</tbody>
</table>
<hr />
<h2>Identity Related Data</h2>
<h3><code>GET /api/v1/identity/:identity_uuid/files</code></h3>
<p><strong>Auth</strong>: Required
<strong>Scope</strong>: identity-level</p>
<p>List all files containing this identity.</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/identity/</span><span class="nv">$IDENTITY_UUID</span><span class="s2">/files&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>
</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;identity_uuid&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;a9a90105-6d6b-46ff-92da-0c3c1a57dff4&quot;</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">3</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="w"> </span><span class="nt">&quot;file_uuid&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;d3f9ae8e471a1fc4d47022c66091b920&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;video1.mp4&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">142</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;first_appearance&quot;</span><span class="p">:</span><span class="w"> </span><span class="mf">4.17</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;last_appearance&quot;</span><span class="p">:</span><span class="w"> </span><span class="mf">208.33</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="p">]</span>
<span class="p">}</span>
</code></pre></div>
<hr />
<h3><code>GET /api/v1/identity/:identity_uuid/chunks</code></h3>
<p><strong>Auth</strong>: Required
<strong>Scope</strong>: identity-level</p>
<p>List all chunks associated with this identity (chunks where the identity's face appears).</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</td>
</tr>
<tr>
<td><code>page_size</code></td>
<td>integer</td>
<td>No</td>
<td>20</td>
<td>Items per page</td>
</tr>
</tbody>
</table>
<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/identity/</span><span class="nv">$IDENTITY_UUID</span><span class="s2">/chunks?page=1&amp;page_size=50&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>
</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;identity_uuid&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;a9a90105-6d6b-46ff-92da-0c3c1a57dff4&quot;</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">45</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;chunks&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;chunk_1&quot;</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;d3f9ae8e471a1fc4d47022c66091b920&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">4.17</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">8.33</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;[4s-8s] Hello, how are you?&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;story_child&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>
<hr />
<h3><code>GET /api/v1/identity/:identity_uuid/faces</code></h3>
<p><strong>Auth</strong>: Required
<strong>Scope</strong>: identity-level</p>
<p>List all face detections for this identity.</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</td>
</tr>
<tr>
<td><code>page_size</code></td>
<td>integer</td>
<td>No</td>
<td>50</td>
<td>Items per page</td>
</tr>
</tbody>
</table>
<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/identity/</span><span class="nv">$IDENTITY_UUID</span><span class="s2">/faces?page=1&amp;page_size=100&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>
</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;identity_uuid&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;a9a90105-6d6b-46ff-92da-0c3c1a57dff4&quot;</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">1420</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">50</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;faces&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;face_id&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;face_100&quot;</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;d3f9ae8e471a1fc4d47022c66091b920&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;frame_number&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">1200</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;timestamp&quot;</span><span class="p">:</span><span class="w"> </span><span class="mf">50.0</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;bbox&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">100</span><span class="p">,</span><span class="w"> </span><span class="mi">50</span><span class="p">,</span><span class="w"> </span><span class="mi">300</span><span class="p">,</span><span class="w"> </span><span class="mi">400</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.95</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;trace_id&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="p">]</span>
<span class="p">}</span>
</code></pre></div>
<hr />
<h3><code>GET /api/v1/identity/:identity_uuid/status</code></h3>
<p><strong>Auth</strong>: Required
<strong>Scope</strong>: identity-level</p>
<p>Get processing/status info for an identity.</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/identity/</span><span class="nv">$IDENTITY_UUID</span><span class="s2">/status&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>
</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;identity_uuid&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;a9a90105-6d6b-46ff-92da-0c3c1a57dff4&quot;</span><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;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;confirmed&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">1420</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;file_count&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;has_embedding&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;has_profile_image&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span>
<span class="p">}</span>
</code></pre></div>
<hr />
<h3><code>GET /api/v1/identity/:identity_uuid/json</code></h3>
<p><strong>Auth</strong>: Required
<strong>Scope</strong>: identity-level</p>
<p>Get the raw identity JSON file (same format as identity.json on disk).</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/identity/</span><span class="nv">$IDENTITY_UUID</span><span class="s2">/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>
</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;version&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;identity_uuid&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;a9a90105-6d6b-46ff-92da-0c3c1a57dff4&quot;</span><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;Audrey Hepburn&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;identity_type&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;people&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;source&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;tmdb&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;confirmed&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;tmdb_id&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">1234</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;tmdb_profile&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;https://image.tmdb.org/...&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;metadata&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{},</span>
<span class="w"> </span><span class="nt">&quot;file_bindings&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;file_uuid&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;d3f9ae8e...&quot;</span><span class="p">,</span><span class="w"> </span><span class="nt">&quot;trace_ids&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">1</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;face_count&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">142</span><span class="p">}</span>
<span class="w"> </span><span class="p">]</span>
<span class="p">}</span>
</code></pre></div>
<hr />
<hr />
<h3><code>POST /api/v1/file/:file_uuid/pending-person</code></h3>
<p><strong>Auth</strong>: Required
<strong>Scope</strong>: file-level</p>
<p>Create a manually managed "pending person" under a specific file. A pending person is an identity with <code>status='pending'</code> and <code>source='manual'</code>, used for unmatched traces that the user wants to manually label before a full identity resolution.</p>
<p>Optionally binds a list of trace IDs to this new identity.</p>
<h4>Request</h4>
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;trace_ids&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">100</span><span class="p">,</span><span class="w"> </span><span class="mi">150</span><span class="p">,</span><span class="w"> </span><span class="mi">200</span><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;Mystery Man #1&quot;</span>
<span class="p">}</span>
</code></pre></div>
<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>trace_ids</code></td>
<td>array[int]</td>
<td>No</td>
<td><code>[]</code></td>
<td>Trace IDs to bind to this pending person</td>
</tr>
<tr>
<td><code>name</code></td>
<td>string</td>
<td>No</td>
<td><code>"Person N"</code></td>
<td>Human-readable name. Auto-generated if omitted</td>
</tr>
</tbody>
</table>
<h4>Example</h4>
<div class="codehilite"><pre><span></span><code><span class="c1"># Create pending person with name and no traces</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/file/</span><span class="nv">$FILE_UUID</span><span class="s2">/pending-person&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>-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;name&quot;: &quot;Unknown Woman #2&quot;, &quot;trace_ids&quot;: []}&#39;</span>
<span class="c1"># Create pending person with auto-name and bind traces</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/file/</span><span class="nv">$FILE_UUID</span><span class="s2">/pending-person&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>-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;trace_ids&quot;: [100, 150, 200]}&#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;message&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Created pending person: Mystery Man #1 (uuid: 4d96b25b-68f0-4c52-b238-d69f7dfd588b)&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;data&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;identity_uuid&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;4d96b25b-68f0-4c52-b238-d69f7dfd588b&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;identity_id&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">55</span><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;Mystery Man #1&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;bound_traces&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span>
<span class="w"> </span><span class="p">}</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>identity_uuid</code></td>
<td>string</td>
<td>UUID of the newly created pending identity</td>
</tr>
<tr>
<td><code>identity_id</code></td>
<td>integer</td>
<td>Internal ID of the new identity</td>
</tr>
<tr>
<td><code>name</code></td>
<td>string</td>
<td>Display name</td>
</tr>
<tr>
<td><code>bound_traces</code></td>
<td>integer</td>
<td>Number of traces bound</td>
</tr>
</tbody>
</table>
<h4>Side Effects</h4>
<ul>
<li>Creates an <code>identities</code> row with <code>status='pending'</code>, <code>source='manual'</code>, <code>file_uuid=&lt;file_uuid&gt;</code></li>
<li>If <code>trace_ids</code> provided: <code>UPDATE face_detections SET identity_id = ...</code> for matching traces</li>
<li>If <code>trace_ids</code> provided: TKG face_track nodes get <code>identity_id</code> / <code>identity_name</code> in properties</li>
<li>Identity JSON file synced to disk</li>
</ul>
<hr />
<h3><code>GET /api/v1/file/:file_uuid/pending-persons</code></h3>
<p><strong>Auth</strong>: Required
<strong>Scope</strong>: file-level</p>
<p>List all pending persons for a file.</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/file/</span><span class="nv">$FILE_UUID</span><span class="s2">/pending-persons&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>
</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;message&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Found 2 pending persons for c36f35685177c981aa139b66bbbccc5b&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;data&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;identity_uuid&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;232ecd08-a2bf-4bd0-bd25-0bd8fb7a7dae&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;identity_id&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;name&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Person 2&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;created_at&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;2026-06-23 17:13:23&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;trace_count&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;bound_traces&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="p">]</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>identity_uuid</code></td>
<td>string</td>
<td>Identity UUID</td>
</tr>
<tr>
<td><code>identity_id</code></td>
<td>integer</td>
<td>Internal identity ID</td>
</tr>
<tr>
<td><code>name</code></td>
<td>string</td>
<td>Display name</td>
</tr>
<tr>
<td><code>created_at</code></td>
<td>string</td>
<td>Creation timestamp</td>
</tr>
<tr>
<td><code>trace_count</code></td>
<td>integer</td>
<td>Number of face traces bound to this pending person</td>
</tr>
<tr>
<td><code>bound_traces</code></td>
<td>array[int]</td>
<td>List of bound trace IDs (currently null, reserved for future expansion)</td>
</tr>
</tbody>
</table>
<h4>Notes</h4>
<ul>
<li>Pending persons are normal <code>identities</code> rows with <code>status='pending'</code> — they can be promoted to confirmed via <code>PATCH /api/v1/identity/:identity_uuid</code> (<code>{"status": "confirmed"}</code>)</li>
<li>They can be merged into known identities via <code>POST /api/v1/identity/:identity_uuid/mergeinto</code></li>
<li>Use <code>GET /api/v1/identity/:identity_uuid/traces</code> to get detailed trace info for each pending person</li>
</ul>
<hr />
<h2>Alias System (BCP 47 Locale Tags)</h2>
<p>Identity aliases support multilingual display names. Aliases are stored in <code>metadata.aliases</code> as an array of <code>{locale, name}</code> objects.</p>
<h3>BCP 47 Locale Tags Reference</h3>
<table class="table">
<thead>
<tr>
<th>Locale</th>
<th>Tag</th>
<th>Example</th>
</tr>
</thead>
<tbody>
<tr>
<td>English</td>
<td><code>en</code></td>
<td>John Smith</td>
</tr>
<tr>
<td>Traditional Chinese</td>
<td><code>zh-TW</code></td>
<td>約翰·史密斯</td>
</tr>
<tr>
<td>Simplified Chinese</td>
<td><code>zh-CN</code></td>
<td>约翰·史密斯</td>
</tr>
<tr>
<td>Japanese</td>
<td><code>ja</code></td>
<td>ジョン・スミス</td>
</tr>
<tr>
<td>Korean</td>
<td><code>ko</code></td>
<td>존 스미스</td>
</tr>
<tr>
<td>Cantonese</td>
<td><code>yue</code></td>
<td>約翰·史密夫</td>
</tr>
<tr>
<td>French</td>
<td><code>fr</code></td>
<td>John Smith (French spelling)</td>
</tr>
<tr>
<td>Spanish</td>
<td><code>es</code></td>
<td>Juan Smith</td>
</tr>
<tr>
<td>Arabic</td>
<td><code>ar</code></td>
<td>جون سميث</td>
</tr>
<tr>
<td>Russian</td>
<td><code>ru</code></td>
<td>Джон Смит</td>
</tr>
<tr>
<td>Thai</td>
<td><code>th</code></td>
<td>จอห์น สมิธ</td>
</tr>
</tbody>
</table>
<p>BCP 47 is the IETF standard for language tags. Format: <code>language</code> (e.g. <code>en</code>, <code>ja</code>) or <code>language-Region</code> (e.g. <code>zh-TW</code>, <code>zh-CN</code>). Region suffix distinguishes regional variants.</p>
<h3>Frontend Display Logic</h3>
<div class="codehilite"><pre><span></span><code><span class="kd">function</span><span class="w"> </span><span class="nx">getDisplayName</span><span class="p">(</span><span class="nx">identity</span><span class="p">,</span><span class="w"> </span><span class="nx">preferredLocale</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="c1">// 1. Exact locale match</span>
<span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">match</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">identity</span><span class="p">.</span><span class="nx">metadata</span><span class="o">?</span><span class="p">.</span><span class="nx">aliases</span><span class="o">?</span><span class="p">.</span><span class="nx">find</span><span class="p">(</span><span class="nx">a</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="nx">a</span><span class="p">.</span><span class="nx">locale</span><span class="w"> </span><span class="o">===</span><span class="w"> </span><span class="nx">preferredLocale</span><span class="p">);</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">match</span><span class="p">)</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nx">match</span><span class="p">.</span><span class="nx">name</span><span class="p">;</span>
<span class="w"> </span><span class="c1">// 2. Language-only match (zh-TW → zh)</span>
<span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">lang</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">preferredLocale</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="s1">&#39;-&#39;</span><span class="p">)[</span><span class="mf">0</span><span class="p">];</span>
<span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">langMatch</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">identity</span><span class="p">.</span><span class="nx">metadata</span><span class="o">?</span><span class="p">.</span><span class="nx">aliases</span><span class="o">?</span><span class="p">.</span><span class="nx">find</span><span class="p">(</span><span class="nx">a</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="nx">a</span><span class="p">.</span><span class="nx">locale</span><span class="p">.</span><span class="nx">startsWith</span><span class="p">(</span><span class="nx">lang</span><span class="p">));</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">langMatch</span><span class="p">)</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nx">langMatch</span><span class="p">.</span><span class="nx">name</span><span class="p">;</span>
<span class="w"> </span><span class="c1">// 3. Fallback to identity.name</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nx">identity</span><span class="p">.</span><span class="nx">name</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<h3>Updating Aliases via PATCH</h3>
<div class="codehilite"><pre><span></span><code><span class="err">PATCH</span><span class="w"> </span><span class="err">/api/v</span><span class="mi">1</span><span class="err">/ide</span><span class="kc">nt</span><span class="err">i</span><span class="kc">t</span><span class="err">y/</span><span class="p">:</span><span class="err">ide</span><span class="kc">nt</span><span class="err">i</span><span class="kc">t</span><span class="err">y_uuid</span>
<span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;metadata&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;aliases&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;locale&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;en&quot;</span><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;John Smith&quot;</span><span class="p">},</span>
<span class="w"> </span><span class="p">{</span><span class="nt">&quot;locale&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;zh-TW&quot;</span><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;約翰·史密斯&quot;</span><span class="p">}</span>
<span class="w"> </span><span class="p">]</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>This <strong>replaces</strong> the entire <code>aliases</code> array. To add to existing aliases, include all existing entries in the request.</p>
<hr />
<p><em>Updated: 2026-06-20 — Added identity files, chunks, faces, status, and JSON endpoints</em></p>
</div>
</body>
</html>