# HyperMS Open API Contract for AI Development Agents

Use this file as the implementation contract when building a third-party tool that reads HyperMS drive-index files, enriches metadata, and imports the enriched items into the HyperMS media library.

Base URL placeholder:

```text
{HYPERMS_BASE_URL}
```

Example:

```text
http://127.0.0.1:8115
```

## Authentication

All requests require administrator session cookies or an API Token with equivalent backend permission.

For third-party desktop tools, use the API Token from:

```text
设置 -> 关于 -> 版本信息 -> API Token
```

Send it in the standard bearer header:

```http
Authorization: Bearer hms_xxxxxxxxxxxxxxxxxxxxx
```

Do not ask users for the administrator password if an API Token is available.

## Common Response Envelope

Every endpoint returns a JSON envelope.

```ts
type HyperMSResponse<T> = {
  success: boolean;
  message?: string;
  data?: T;
  items?: unknown[];
  total?: number;
};
```

For the endpoints in this document, read business payloads from `data` unless the endpoint explicitly documents `items`.

## Required Workflow

Implement this flow:

1. Ask the user for `{HYPERMS_BASE_URL}`, `API Token`, and `root_pk`.
2. Call the export endpoint to list indexed files.
3. Let the user or an external metadata provider fill metadata.
4. Build one import item per movie, TV season, custom category item, or resource group.
5. Import items with file references from the export response.
6. Show `imported_count`, `failed_count`, `items`, and `failed_items` to the user.

Never invent required values. If the user does not provide `title` or `media_type`, stop and report the missing field.

## Endpoint 1: Export Drive Index Files

```http
GET /api/v1/drive/index/roots/{root_pk}/export-files?video_only=true&include_dirs=false&limit=1000&offset=0
```

Purpose: list indexed files under one drive-index root. Use the returned `items[].id` or `items[].file_id` as file references when importing metadata.

### Request

Path params:

```ts
type ExportPathParams = {
  root_pk: number; // required, must be > 0
};
```

Query params:

```ts
type ExportQuery = {
  video_only?: boolean;   // default true
  include_dirs?: boolean; // default false
  limit?: number;         // default 1000, valid range 1..10000
  offset?: number;        // default 0
};
```

Recommended query for metadata import tools:

```text
video_only=true&include_dirs=false&limit=1000&offset=0
```

Use pagination until `offset + items.length >= total`.

### Response Data

```ts
type ExportFilesData = {
  root: DriveIndexRoot;
  items: DriveIndexFile[];
  total: number;
  limit: number;
  offset: number;
  video_only: boolean;
  include_dirs: boolean;
};

type DriveIndexRoot = {
  id: number;              // same as root_pk for import
  drive_type: string;      // import currently supports "115"
  root_id: string;
  root_name: string;
  root_path: string;
  file_count: number;
  video_count: number;
  indexed_at: string | null;
  created_at: string;
  updated_at: string;
};

type DriveIndexFile = {
  id: number;              // database file id; use in import item.file_pks
  root_pk: number;
  file_id: string;         // drive-side file id; use in import item.file_ids
  parent_id: string;
  file_name: string;
  file_path: string;
  relative_path: string;
  file_size: number;
  pickcode: string;        // 115 pickcode, used later for playback
  sha1: string;
  is_dir: boolean;
  is_video: boolean;
  modified_at: string;
  created_at: string;
};
```

### Export Example

```bash
curl -sS \
  -H "Authorization: Bearer hms_xxxxxxxxxxxxxxxxxxxxx" \
  "{HYPERMS_BASE_URL}/api/v1/drive/index/roots/2/export-files?video_only=true&include_dirs=false&limit=1000&offset=0"
```

## Endpoint 2: Import Enriched Metadata Into Media Library

```http
POST /api/v1/library/drive-index/import
```

Purpose: write enriched metadata and file bindings into the HyperMS media library. A successful import creates or updates media records, resource groups, and bindings, then attempts to trigger Emby HyperMS channel sync.

### Required Validation

Top-level required fields:

- `root_pk`
- `items`

Per-item required fields:

- `title`
- `media_type`
- at least one file reference matching the current `root_pk`

File reference fields:

- `file_pks`
- `file_ids`
- `files`

At least one of those three must be present for each item.

`tmdb_id` is optional. Do not generate a fake `tmdb_id`. Do not infer missing metadata unless the user or an upstream metadata source provides it.

### Request Payload

```ts
type ImportRequest = {
  root_pk: number;          // required, existing 115 drive-index root id
  source_key?: string;      // default "external"
  items: ImportItem[];      // required, non-empty
};

type ImportItem = {
  title: string;            // required, non-empty
  media_type: string;       // required; "movie", "tv", or "custom"

  tmdb_id?: string;         // optional
  douban_id?: string;
  original_title?: string;
  year?: number | null;
  season_number?: number | null;
  language?: string;
  poster_url?: string;
  backdrop_url?: string;
  overview?: string;
  genres?: string[];

  // If both extra and extra_json are provided, prefer extra_json.
  extra?: Record<string, unknown>;
  extra_json?: Record<string, unknown>;

  // For custom media type/category import.
  custom_category_id?: string;   // preferred when available
  custom_category_path?: string; // e.g. "综艺/真人秀"

  // Resource grouping metadata.
  group_cid?: string;
  group_name?: string;
  group_path?: string;
  parent_group_cid?: string;
  parent_group_name?: string;
  variant_label?: string;
  variant_type?: string;
  match_note?: string;
  match_confidence?: number | null;
  is_primary?: boolean;     // default true

  // File references. Provide at least one of these.
  file_pks?: number[];      // from export items[].id
  file_ids?: string[];      // from export items[].file_id
  files?: ImportFileRef[];
};

type ImportFileRef = {
  id?: number;              // alias of file_pk
  file_pk?: number;         // from export items[].id
  file_id?: string;         // from export items[].file_id
  fid?: string;             // alias of file_id
};
```

### `media_type` Semantics

Use:

- `movie` for normal movie media.
- `tv` for normal TV series or seasons.
- `custom` for custom top-level media types, such as variety shows. Use `custom_category_id` or `custom_category_path` to place the item under a custom category.

Custom category paths use:

```text
一级目录/二级目录
```

Example:

```text
综艺/真人秀
```

If a custom category ID is visible in the HyperMS UI, prefer `custom_category_id` over path matching.

### `extra_json` Common Keys

`extra_json` is saved as-is on the media record. It can store arbitrary JSON. Recommended keys:

```ts
type RecommendedExtraJson = {
  credits?: object;                 // raw TMDB credits object
  cast?: unknown[];                 // actor list
  actors?: unknown[];               // alias of cast
  crew?: unknown[];                 // raw crew list
  director?: unknown[];             // director list
  directors?: unknown[];            // alias of director
  rating?: number;
  tmdb_rating?: number;
  vote_count?: number;
  runtime?: number;                 // minutes
  tagline?: string;
  status?: string;
  homepage?: string;
  imdb_id?: string;
  original_language?: string;
  country?: string;
  region?: string;
  production_companies?: unknown[];
  production_countries?: unknown[];
  spoken_languages?: unknown[];
  keywords?: unknown[] | object;
  episode_metadata?: Record<string, unknown>;
  custom_category?: object;

  // Custom keys are allowed.
  [key: string]: unknown;
};
```

### Import Response Data

```ts
type ImportResultData = {
  root_pk: number;
  share_link_id: number;
  imported_count: number;
  failed_count: number;
  items: ImportedItem[];
  failed_items: FailedImportItem[];
  message: string;
};

type ImportedItem = {
  media_item_id: number;
  resource_group_id: number;
  tmdb_id: string;
  media_type: string;
  title: string;
  custom_category: object | null;
  file_count: number;
  video_count: number;
};

type FailedImportItem = {
  index: number;       // 1-based item index in request.items
  tmdb_id: string;
  media_type: string;
  title: string;
  message: string;
};
```

Treat `failed_count > 0` as a partial failure even if the HTTP status is successful.

### Minimal Import Example

```json
{
  "root_pk": 2,
  "source_key": "manual-test",
  "items": [
    {
      "title": "示例真人秀",
      "media_type": "custom",
      "year": 2026,
      "custom_category_path": "综艺/真人秀",
      "file_pks": [12345]
    }
  ]
}
```

### Import Curl Example

```bash
curl -sS \
  -X POST \
  -H "Authorization: Bearer hms_xxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "root_pk": 2,
    "source_key": "external-tool",
    "items": [
      {
        "title": "示例真人秀",
        "media_type": "custom",
        "custom_category_path": "综艺/真人秀",
        "file_pks": [12345],
        "extra_json": {
          "cast": ["演员 A", "演员 B"],
          "runtime": 90
        }
      }
    ]
  }' \
  "{HYPERMS_BASE_URL}/api/v1/library/drive-index/import"
```

## Client Implementation Checklist

Before calling import:

- Confirm `root_pk` exists by calling export successfully.
- Filter out directories unless the user intentionally imported directories.
- Prefer video files only.
- Group files into one import item when they belong to the same media title or season.
- Validate `title` and `media_type`.
- Validate at least one file reference per item.
- Use exported `items[].id` as `file_pks` when possible.
- Use `custom_category_id` if the user selected a custom category by ID.
- Use `custom_category_path` only when no ID is available.
- Preserve user-provided metadata exactly. Do not auto-fill fallback values.

After calling import:

- Display `imported_count`.
- Display every `failed_items[].message`.
- If `failed_count > 0`, allow the user to edit failed items and retry only those items.
- Do not retry successful items unless the user explicitly asks.
