# Identity API Specification

> Version: V4.0 | Date: 2026-04-28
> Architecture: Two-layer (Face → Identity)
> Base URL: `http://localhost:3003/api/v1`

---

## Overview

| Category | Count | Description |
|----------|-------|-------------|
| **List API** | 6 | One-to-many queries |
| **Candidates API** | 2 | Unregistered face candidates |
| **Suggest API** | 2 | AI clustering suggestions |
| **Detail API** | 2 | Single item detail |
| **Register/Bind API** | 3 | Identity management operations |
| **Total** | **15** | Core endpoints |

---

## Terminology

| Term | Type | Description |
|------|------|-------------|
| **file_uuid** | UUID | Video file identifier |
| **identity_uuid** | UUID | Global identity identifier |
| **face_id** | string | Single face detection |
| **trace_id** | int | Face tracking ID |
| **chunk_id** | string | Sentence chunk ID |

---

## Pagination Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `page` | int | 1 | Page number (>=1) |
| `page_size` | int | 15 | Items per page (1-100) |
| `limit` | int | null | Total items limit |
| `search` | string | null | Search query |
| `sort` | string | created_at | Sort field |
| `order` | string | DESC | Sort direction (ASC/DESC) |

---

## Response Format

### List API Response

```json
{
  "success": true,
  "data": {
    "[items]": [...],
    "pagination": {
      "page": 1,
      "page_size": 15,
      "total": 100,
      "total_pages": 7,
      "limit": null
    }
  }
}
```

### Detail API Response

```json
{
  "success": true,
  "data": {
    "[item]": {...}
  }
}
```

### Error Response

```json
{
  "success": false,
  "error": {
    "code": "NOT_FOUND",
    "message": "Identity not found",
    "details": {}
  }
}
```

---

## 1. List API (One-to-Many)

---

### 1.1 GET /api/v1/files

List all files.

**Parameters**:

| Parameter | Type | Required | Default |
|-----------|------|----------|---------|
| `page` | int | No | 1 |
| `page_size` | int | No | 15 |
| `limit` | int | No | null |
| `search` | string | No | null |
| `status` | string | No | null |

**Request**:
```bash
curl "http://localhost:3003/api/v1/files?page=1&page_size=15" \
  -H "X-API-Key: YOUR_KEY"
```

**Response**:
```json
{
  "success": true,
  "data": {
    "files": [
      {
        "file_uuid": "384b0ff44aaaa1f14cb2cd63b3fea966",
        "file_name": "Charade_1963.mp4",
        "duration": 6879.33,
        "status": "completed",
        "total_identities": 5,
        "total_faces": 800,
        "created_at": "2026-04-28T10:00:00Z"
      }
    ],
    "pagination": {
      "page": 1,
      "page_size": 15,
      "total": 100,
      "total_pages": 7
    }
  }
}
```

---

### 1.2 GET /api/v1/identities

List all identities.

**Parameters**:

| Parameter | Type | Required | Default |
|-----------|------|----------|---------|
| `page` | int | No | 1 |
| `page_size` | int | No | 15 |
| `limit` | int | No | null |
| `search` | string | No | null |
| `source` | string | No | null |

**Request**:
```bash
curl "http://localhost:3003/api/v1/identities?page=1&page_size=15" \
  -H "X-API-Key: YOUR_KEY"
```

**Response**:
```json
{
  "success": true,
  "data": {
    "identities": [
      {
        "identity_uuid": "a9a90105-6d6b-...",
        "name": "Audrey Hepburn",
        "source": "manual",
        "total_files": 3,
        "total_faces": 1500,
        "reference_vectors": {
          "total": 4,
          "angles": ["frontal", "profile_right"]
        },
        "created_at": "2026-04-28T10:00:00Z"
      }
    ],
    "pagination": {
      "page": 1,
      "page_size": 15,
      "total": 50,
      "total_pages": 4
    }
  }
}
```

---

### 1.3 GET /api/v1/identities/:identity_uuid/files

List files where identity appears (N:N relationship).

**Parameters**:

| Parameter | Type | Required | Default |
|-----------|------|----------|---------|
| `identity_uuid` | UUID | Yes | - |
| `page` | int | No | 1 |
| `page_size` | int | No | 15 |
| `status` | string | No | null |

**Request**:
```bash
curl "http://localhost:3003/api/v1/identities/a9a90105.../files" \
  -H "X-API-Key: YOUR_KEY"
```

**Response**:
```json
{
  "success": true,
  "data": {
    "identity_uuid": "a9a90105...",
    "name": "Audrey Hepburn",
    "files": [
      {
        "file_uuid": "384b0ff44aaaa1f14cb2cd63b3fea966",
        "file_name": "Charade_1963.mp4",
        "face_count": 500,
        "speaker_count": 10,
        "first_appearance": 5.2,
        "last_appearance": 180.5,
        "confidence": 0.86
      }
    ],
    "total_files": 2
  }
}
```

---

### 1.4 GET /api/v1/files/:file_uuid/identities

List identities in a file (N:N relationship).

**Parameters**:

| Parameter | Type | Required | Default |
|-----------|------|----------|---------|
| `file_uuid` | UUID | Yes | - |
| `page` | int | No | 1 |
| `page_size` | int | No | 15 |
| `status` | string | No | null |

**Request**:
```bash
curl "http://localhost:3003/api/v1/files/384b0ff44aaaa1f14cb2cd63b3fea966/identities" \
  -H "X-API-Key: YOUR_KEY"
```

**Response**:
```json
{
  "success": true,
  "data": {
    "file_uuid": "384b0ff44aaaa1f14cb2cd63b3fea966",
    "file_name": "Charade_1963.mp4",
    "identities": [
      {
        "identity_uuid": "a9a90105...",
        "name": "Audrey Hepburn",
        "face_count": 500,
        "speaker_count": 10,
        "confidence": 0.86
      }
    ],
    "total_identities": 5
  }
}
```

---

### 1.5 GET /api/v1/identities/:identity_uuid/faces

List faces bound to an identity.

**Parameters**:

| Parameter | Type | Required | Default |
|-----------|------|----------|---------|
| `identity_uuid` | UUID | Yes | - |
| `page` | int | No | 1 |
| `page_size` | int | No | 100 |
| `limit` | int | No | 1000 |
| `pose_angle` | string | No | null |

**Request**:
```bash
curl "http://localhost:3003/api/v1/identities/a9a90105.../faces?page_size=100" \
  -H "X-API-Key: YOUR_KEY"
```

**Response**:
```json
{
  "success": true,
  "data": {
    "identity_uuid": "a9a90105...",
    "faces": [
      {
        "face_id": "face_100",
        "file_uuid": "384b0ff44aaaa1f14cb2cd63b3fea966",
        "frame": 100,
        "timestamp": 5.2,
        "pose_angle": "frontal",
        "confidence": 0.92,
        "trace_id": 2
      }
    ],
    "total_faces": 1500,
    "pose_distribution": {
      "frontal": 400,
      "profile_right": 300
    }
  }
}
```

---

### 1.6 GET /api/v1/identities/:identity_uuid/chunks

List chunks bound to an identity.

**Parameters**:

| Parameter | Type | Required | Default |
|-----------|------|----------|---------|
| `identity_uuid` | UUID | Yes | - |
| `page` | int | No | 1 |
| `page_size` | int | No | 50 |
| `limit` | int | No | 500 |
| `speaker_id` | string | No | null |

**Request**:
```bash
curl "http://localhost:3003/api/v1/identities/a9a90105.../chunks" \
  -H "X-API-Key: YOUR_KEY"
```

**Response**:
```json
{
  "success": true,
  "data": {
    "identity_uuid": "a9a90105...",
    "chunks": [
      {
        "chunk_id": "chunk_1",
        "file_uuid": "384b0ff44aaaa1f14cb2cd63b3fea966",
        "text": "Hello, how are you?",
        "start_time": 5.2,
        "end_time": 8.5,
        "speaker_id": "SPEAKER_0"
      }
    ],
    "total_chunks": 30,
    "speaker_ids": ["SPEAKER_0"],
    "total_duration": 45.5
  }
}
```

---

## 2. Candidates API (Unregistered)

---

### 2.1 GET /api/v1/faces/candidates

List unregistered faces (identity_id = NULL).

**Parameters**:

| Parameter | Type | Required | Default |
|-----------|------|----------|---------|
| `file_uuid` | UUID | No | null |
| `min_confidence` | float | No | 0.5 |
| `pose_angle` | string | No | null |
| `page` | int | No | 1 |
| `page_size` | int | No | 15 |
| `limit` | int | No | 100 |

**Request**:
```bash
curl "http://localhost:3003/api/v1/faces/candidates?min_confidence=0.8&pose_angle=frontal" \
  -H "X-API-Key: YOUR_KEY"
```

**Response**:
```json
{
  "success": true,
  "data": {
    "candidates": [
      {
        "face_id": "face_100",
        "file_uuid": "384b0ff44aaaa1f14cb2cd63b3fea966",
        "frame": 100,
        "timestamp": 5.2,
        "pose_angle": "frontal",
        "confidence": 0.92,
        "trace_id": 2,
        "embedding_quality": 0.88
      }
    ],
    "statistics": {
      "total_candidates": 78,
      "pose_distribution": {
        "frontal": 20,
        "profile_right": 30
      },
      "avg_confidence": 0.85
    },
    "pagination": {
      "page": 1,
      "page_size": 15,
      "total": 78,
      "total_pages": 6
    }
  }
}
```

---

### 2.2 GET /api/v1/files/:file_uuid/faces/candidates

List unregistered faces in a specific file.

**Parameters**:

| Parameter | Type | Required | Default |
|-----------|------|----------|---------|
| `file_uuid` | UUID | Yes | - |
| `min_confidence` | float | No | 0.5 |
| `page` | int | No | 1 |
| `page_size` | int | No | 15 |

**Request**:
```bash
curl "http://localhost:3003/api/v1/files/384b0ff44aaaa1f14cb2cd63b3fea966/faces/candidates" \
  -H "X-API-Key: YOUR_KEY"
```

---

## 3. Suggest API (AI Agent)

---

### 3.1 POST /api/v1/agents/suggest/clustering

AI clustering suggestions for unregistered faces.

**Request Body**:
```json
{
  "file_uuid": "384b0ff44aaaa1f14cb2cd63b3fea966",
  "min_confidence": 0.8,
  "pose_angles": ["frontal"],
  "clustering_threshold": 0.85,
  "max_suggestions": 5
}
```

**Request**:
```bash
curl -X POST "http://localhost:3003/api/v1/agents/suggest/clustering" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: YOUR_KEY" \
  -d '{"min_confidence": 0.8, "max_suggestions": 5}'
```

**Response**:
```json
{
  "success": true,
  "data": {
    "suggestions": [
      {
        "suggestion_id": "suggest_1",
        "cluster_type": "high_confidence",
        "confidence": 0.92,
        "recommended_faces": [
          {
            "face_id": "face_100",
            "pose_angle": "frontal",
            "confidence": 0.95,
            "is_primary": true
          }
        ],
        "cluster_stats": {
          "total_faces": 50,
          "avg_similarity": 0.89,
          "trace_ids": [2, 3]
        },
        "reason": "High confidence frontal faces from same trace",
        "action": "register"
      }
    ],
    "analysis_summary": {
      "total_candidates": 78,
      "potential_clusters": 5,
      "suggested_actions": {
        "register": 3,
        "bind": 2
      }
    }
  }
}
```

---

### 3.2 POST /api/v1/agents/suggest/merge

AI merge suggestions for identities.

**Request Body**:
```json
{
  "identity_uuids": ["a9a90105...", "b8b80206..."],
  "threshold": 0.85
}
```

**Request**:
```bash
curl -X POST "http://localhost:3003/api/v1/agents/suggest/merge" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: YOUR_KEY" \
  -d '{"identity_uuids": ["a9a90105...", "b8b80206..."]}'
```

**Response**:
```json
{
  "success": true,
  "data": {
    "suggestions": [
      {
        "suggestion_type": "merge",
        "confidence": 0.88,
        "identities": [
          {"identity_uuid": "a9a90105...", "name": "Person A", "face_count": 500},
          {"identity_uuid": "b8b80206...", "name": "Person B", "face_count": 300}
        ],
        "reason": "High embedding similarity (0.88)",
        "recommended_action": {
          "merge_target": "a9a90105...",
          "merge_sources": ["b8b80206..."]
        }
      }
    ]
  }
}
```

---

## 4. Detail API (One-to-One)

---

### 4.1 GET /api/v1/identities/:identity_uuid

Identity detail.

**Parameters**:

| Parameter | Type | Required |
|-----------|------|----------|
| `identity_uuid` | UUID | Yes |

**Request**:
```bash
curl "http://localhost:3003/api/v1/identities/a9a90105..." \
  -H "X-API-Key: YOUR_KEY"
```

**Response**:
```json
{
  "success": true,
  "data": {
    "identity_uuid": "a9a90105...",
    "name": "Audrey Hepburn",
    "source": "manual",
    "identity_type": "person",
    "global_stats": {
      "total_files": 3,
      "total_faces": 1500,
      "total_speaker_segments": 30
    },
    "reference_vectors": {
      "total": 4,
      "angles": ["frontal", "profile_right"],
      "quality_avg": 0.875
    },
    "created_at": "2026-04-28T10:00:00Z"
  }
}
```

---

### 4.2 GET /api/v1/files/:file_uuid

File detail.

**Parameters**:

| Parameter | Type | Required |
|-----------|------|----------|
| `file_uuid` | UUID | Yes |

**Request**:
```bash
curl "http://localhost:3003/api/v1/files/384b0ff44aaaa1f14cb2cd63b3fea966" \
  -H "X-API-Key: YOUR_KEY"
```

**Response**:
```json
{
  "success": true,
  "data": {
    "file_uuid": "384b0ff44aaaa1f14cb2cd63b3fea966",
    "file_name": "Charade_1963.mp4",
    "duration": 6879.33,
    "status": "completed",
    "identity_stats": {
      "total_identities": 5,
      "identities": [
        {"identity_uuid": "a9a90105...", "name": "Audrey Hepburn", "face_count": 500}
      ]
    }
  }
}
```

---

## 5. Register/Bind API

---

### 5.1 POST /api/v1/identities/register

Register new identity from faces.

**Request Body**:
```json
{
  "face_ids": ["face_100", "face_150", "face_200"],
  "name": "Audrey Hepburn",
  "source": "manual",
  "auto_bind_chunks": true
}
```

**Request**:
```bash
curl -X POST "http://localhost:3003/api/v1/identities/register" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: YOUR_KEY" \
  -d '{
    "face_ids": ["face_100"],
    "name": "Audrey Hepburn",
    "auto_bind_chunks": true
  }'
```

**Response**:
```json
{
  "success": true,
  "data": {
    "identity_uuid": "a9a90105-...",
    "name": "Audrey Hepburn",
    "faces_bound": 3,
    "chunks_bound": 10,
    "speaker_ids": ["SPEAKER_0"],
    "reference_vectors": {
      "total": 3,
      "angles": ["frontal"]
    }
  }
}
```

---

### 5.2 POST /api/v1/identities/:identity_uuid/bind

Bind additional faces to existing identity.

**Request Body**:
```json
{
  "face_ids": ["face_300", "face_400"],
  "auto_bind_chunks": true
}
```

**Request**:
```bash
curl -X POST "http://localhost:3003/api/v1/identities/a9a90105.../bind" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: YOUR_KEY" \
  -d '{"face_ids": ["face_300"]}'
```

**Response**:
```json
{
  "success": true,
  "data": {
    "identity_uuid": "a9a90105...",
    "faces_bound": 1,
    "chunks_bound": 3
  }
}
```

---

### 5.3 POST /api/v1/identities/:identity_uuid/unbind

Unbind faces from identity.

**Request Body**:
```json
{
  "face_ids": ["face_400"]
}
```

**Request**:
```bash
curl -X POST "http://localhost:3003/api/v1/identities/a9a90105.../unbind" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: YOUR_KEY" \
  -d '{"face_ids": ["face_400"]}'
```

**Response**:
```json
{
  "success": true,
  "data": {
    "identity_uuid": "a9a90105...",
    "faces_unbound": 1
  }
}
```

---

## 6. Error Codes

| Code | HTTP Status | Description |
|------|-------------|-------------|
| `NOT_FOUND` | 404 | Resource not found |
| `BAD_REQUEST` | 400 | Invalid request |
| `UNAUTHORIZED` | 401 | Invalid API key |
| `INTERNAL_ERROR` | 500 | Server error |
| `VALIDATION_ERROR` | 422 | Validation failed |

---

## 7. Authentication

All endpoints require API key in header:

```bash
-H "X-API-Key: YOUR_API_KEY"
```

---

## Version History

| Version | Date | Changes |
|---------|------|---------|
| V4.0 | 2026-04-28 | Two-layer architecture, 15 core endpoints |
| V3.x | 2026-04-10 | 33 endpoints (many deprecated) |

---

## Deprecated Endpoints (V3.x → V4.0)

| Endpoint | Status | Replacement |
|----------|--------|--------------|
| `/api/v1/person/list` | ❌ Removed | `/api/v1/faces/candidates` |
| `/api/v1/person/:id` | ❌ Removed | `/api/v1/identities/:uuid` |
| `/api/v1/person/merge` | ❌ Removed | `/api/v1/agents/suggest/merge` |
| `/api/v1/person/:id/split` | ❌ Removed | Manual face re-binding |
| `/api/v1/chunks/candidates` | ❌ Removed | Chunks auto-bind |
| **26 more person APIs** | ❌ Removed | See above replacements |
