diff --git a/app/agent/api/executor.py b/app/agent/api/executor.py index dee4c3991..7886e5273 100644 --- a/app/agent/api/executor.py +++ b/app/agent/api/executor.py @@ -8,7 +8,12 @@ from typing import Any, Mapping from urllib.parse import quote from app.adapters.network.http import AsyncHttpRequestError, AsyncRequestUtils -from app.agent.policy.api import ApiOperationRoute, resolve_api_operation, resolve_api_route +from app.agent.policy.api import ( + API_UNBOUND_READ_OPERATIONS, + ApiOperationRoute, + resolve_api_operation, + resolve_api_route, +) from app.agent.policy.contracts import ActionEffect from app.agent.policy.sanitizer import stable_type_name from app.application.security.token import create_access_token @@ -38,6 +43,8 @@ class ApiExecutionContext: session_id: str | None = None channel: str | None = None source: str | None = None + # 使用宿主身份的公共查询不能复用于其他 operation。 + public_read_only: bool = False class MoviePilotApiExecutor: @@ -166,6 +173,8 @@ class MoviePilotApiExecutor: body: Any = None, ) -> str: """执行白名单 operation,并把响应转换为稳定 JSON 文本。""" + if self._context.public_read_only and operation_id not in API_UNBOUND_READ_OPERATIONS: + raise ApiExecutionError("公共查询执行器不允许访问个人数据或执行其他操作") route = resolve_api_route(operation_id) if route is None: raise ApiExecutionError(f"未注册 API 路由: {operation_id}") diff --git a/app/agent/policy/api.py b/app/agent/policy/api.py index b0464eadb..6e04fbc4f 100644 --- a/app/agent/policy/api.py +++ b/app/agent/policy/api.py @@ -1,6 +1,7 @@ """Agent 结构化 MoviePilot API 操作与固定路由注册表。""" from dataclasses import dataclass +from enum import Enum from typing import Optional from app.agent.policy.contracts import ( @@ -12,6 +13,14 @@ from app.agent.policy.contracts import ( ) +class ApiIdentityScope(str, Enum): + """区分公共资料、绑定用户数据和受保护的实例管理身份。""" + + PUBLIC = "public" + BOUND_USER = "bound_user" + ADMIN = "admin" + + @dataclass(frozen=True) class ApiOperationSpec: """描述一个可由 Agent API 网关调用的稳定业务操作。""" @@ -22,6 +31,17 @@ class ApiOperationSpec: confirmation: ConfirmationMode recovery: RecoveryMode result_sensitivity: ResultSensitivity + identity_scope: ApiIdentityScope = ApiIdentityScope.BOUND_USER + + def __post_init__(self) -> None: + """公共身份只接受无需确认、无私密结果的普通用户级安全查询。""" + if self.identity_scope is ApiIdentityScope.PUBLIC and ( + self.effect is not ActionEffect.SAFE_READ + or self.required_role is not PrincipalRole.USER + or self.confirmation is not ConfirmationMode.NONE + or self.result_sensitivity is not ResultSensitivity.NORMAL + ): + raise ValueError("公共 API 身份不能用于敏感读取、管理员操作或写操作") @dataclass(frozen=True) @@ -46,6 +66,7 @@ def _spec( confirmation: ConfirmationMode = ConfirmationMode.NONE, recovery: RecoveryMode = RecoveryMode.NONE, result_sensitivity: ResultSensitivity = ResultSensitivity.NORMAL, + identity_scope: Optional[ApiIdentityScope] = None, ) -> ApiOperationSpec: """构造不可变 API 操作描述。""" return ApiOperationSpec( @@ -55,9 +76,17 @@ def _spec( confirmation=confirmation, recovery=recovery, result_sensitivity=result_sensitivity, + identity_scope=identity_scope or ( + ApiIdentityScope.ADMIN if required_role is _ADMIN else ApiIdentityScope.BOUND_USER + ), ) +def _public_read(operation_id: str) -> ApiOperationSpec: + """仅为已审计、不读取本地用户数据的公共资料声明无绑定查询。""" + return _spec(operation_id, identity_scope=ApiIdentityScope.PUBLIC) + + def _write( operation_id: str, *, @@ -103,20 +132,20 @@ def _user_write( API_FIRST_BATCH_OPERATION_SPECS: tuple[ApiOperationSpec, ...] = ( - _spec("media.search"), - _spec("media.person.search"), - _spec("media.person.credits"), + _public_read("media.search"), + _public_read("media.person.search"), + _public_read("media.person.credits"), _spec("media.recognize"), _spec("media.scrape", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, required_role=_ADMIN, confirmation=_CONFIRM), - _spec("media.episode_schedule"), - _spec("media.detail"), + _public_read("media.episode_schedule"), + _public_read("media.detail"), # 订阅归属由 API 当前用户决定,渠道管理员也必须使用绑定用户身份。 _user_write("subscription.add"), _user_write("subscription.update"), _user_write("subscription.search", effect=ActionEffect.EXTERNAL_SIDE_EFFECT), _spec("subscription.list"), - _spec("subscription.shares"), - _spec("subscription.popular"), + _public_read("subscription.shares"), + _public_read("subscription.popular"), _spec("subscription.history"), _user_write("subscription.delete", effect=ActionEffect.DESTRUCTIVE_WRITE, recovery=_DELETE_RECOVERABLE), _spec("download.add", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, confirmation=_CONFIRM, recovery=_IDEMPOTENT), @@ -137,7 +166,7 @@ API_FIRST_BATCH_OPERATION_SPECS: tuple[ApiOperationSpec, ...] = ( _admin_read("site.userdata", sensitivity=ResultSensitivity.PRIVATE), _spec("site.test"), _write("site.cookie.update", sensitivity=ResultSensitivity.PRIVATE), - _spec("recommendation.list"), + _public_read("recommendation.list"), _spec("library.exists"), _spec("library.latest"), _admin_read("storage.settings"), @@ -234,13 +263,13 @@ API_PARITY_OPERATION_SPECS: tuple[ApiOperationSpec, ...] = ( ) API_MUSIC_OPERATION_SPECS: tuple[ApiOperationSpec, ...] = ( - _spec("music.recognize"), - _spec("music.explore"), - _spec("music.album.get"), - _spec("music.album.related"), - _spec("music.artist.get"), - _spec("music.artist.albums"), - _spec("music.artist.related"), + _public_read("music.recognize"), + _public_read("music.explore"), + _public_read("music.album.get"), + _public_read("music.album.related"), + _public_read("music.artist.get"), + _public_read("music.artist.albums"), + _public_read("music.artist.related"), _admin_read("music.cache.get", sensitivity=ResultSensitivity.PRIVATE), _write( "music.cache.delete", @@ -318,9 +347,9 @@ API_EXTENDED_OPERATION_SPECS: tuple[ApiOperationSpec, ...] = ( _admin_read("media.classification.policy.history", sensitivity=ResultSensitivity.PRIVATE), _write("media.classification.policy.update", recovery=RecoveryMode.TRANSACTION), _write("media.classification.policy.rollback", recovery=RecoveryMode.TRANSACTION), - _spec("media.episode_groups"), - _spec("media.episode_group.seasons"), - _spec("media.seasons"), + _public_read("media.episode_groups"), + _public_read("media.episode_group.seasons"), + _public_read("media.seasons"), _spec("search.title", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, recovery=_IDEMPOTENT), _spec("search.recommend", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, recovery=_IDEMPOTENT), _spec("subtitle.search.title", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, recovery=_IDEMPOTENT), @@ -426,7 +455,7 @@ API_EXTENDED_OPERATION_SPECS: tuple[ApiOperationSpec, ...] = ( _spec("subscription.follow.list"), _user_write("subscription.follow.add"), _user_write("subscription.follow.delete"), - _spec("subscription.share.statistics"), + _public_read("subscription.share.statistics"), _spec( "storage.manage", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, @@ -549,10 +578,10 @@ API_EXTENDED_OPERATION_SPECS: tuple[ApiOperationSpec, ...] = ( _admin_read("plugin.runtime.status", sensitivity=ResultSensitivity.PRIVATE), _admin_read("plugin.history"), _admin_read("plugin.releases"), - _spec("plugin.ratings"), - _spec("plugin.rating"), - _user_write("plugin.rating.submit", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, recovery=_IDEMPOTENT), - _spec("plugin.statistics"), + _admin_read("plugin.ratings", sensitivity=ResultSensitivity.PRIVATE), + _admin_read("plugin.rating", sensitivity=ResultSensitivity.PRIVATE), + _write("plugin.rating.submit", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, recovery=_IDEMPOTENT, sensitivity=ResultSensitivity.PRIVATE), + _public_read("plugin.statistics"), _write( "plugin.reset", effect=ActionEffect.DESTRUCTIVE_WRITE, @@ -594,6 +623,10 @@ API_OPERATION_SPECS: tuple[ApiOperationSpec, ...] = ( *API_EXTENDED_OPERATION_SPECS, ) API_OPERATION_BY_ID = {spec.operation_id: spec for spec in API_OPERATION_SPECS} +# 身份范围是唯一事实源;不能按 safe_read 批量放行实例、文件或用户数据。 +API_UNBOUND_READ_OPERATIONS = frozenset( + spec.operation_id for spec in API_OPERATION_SPECS if spec.identity_scope is ApiIdentityScope.PUBLIC +) # 所有路径均由宿主注册,调用方只能传路径参数、查询参数和 JSON body。 @@ -876,6 +909,7 @@ def list_api_operation_ids() -> tuple[str, ...]: __all__ = [ + "API_UNBOUND_READ_OPERATIONS", "API_FIRST_BATCH_OPERATION_SPECS", "API_EXTENDED_OPERATION_SPECS", "API_MUSIC_OPERATION_SPECS", @@ -884,6 +918,7 @@ __all__ = [ "API_OPERATION_SPECS", "API_PARITY_OPERATION_SPECS", "API_SYSTEM_OPERATION_SPECS", + "ApiIdentityScope", "ApiOperationRoute", "ApiOperationSpec", "list_api_operation_ids", diff --git a/app/agent/policy/mcp.py b/app/agent/policy/mcp.py index 94ad09a35..39ba95667 100644 --- a/app/agent/policy/mcp.py +++ b/app/agent/policy/mcp.py @@ -66,7 +66,7 @@ OPERATION_DESCRIPTIONS = { "plugin.source.install": "Install an unbound plugin from one explicitly selected online source.", "plugin.source.options": "Inspect source candidates and the current immutable source identity before installation or source change.", "plugin.uninstall": "Uninstall one plugin and remove it from the installed set.", - "recommendation.list": "Read personalized media or music recommendations.", + "recommendation.list": "Read public media or music charts and recommendations.", "scheduler.list": "List registered scheduler jobs and their current state.", "scheduler.run": "Run one registered scheduler job immediately.", "search.results": "Read the most recent torrent-search context and result set.", @@ -390,6 +390,7 @@ FIELD_DESCRIPTIONS = { "mode": "Operation mode; music.explore accepts chart or fresh, while transfer history records move, copy, link, or softlink.", "modify_time": "Storage item modification timestamp.", "mtype": "MoviePilot media type or subscription-history category required by the operation.", + "music_cue_enable": "Optional per-subscription CUE organization setting; omit to inherit the system default.", "music_type": "Music identity level: recording, album, or artist where supported.", "include_candidates": "Include unconfirmed music resources and related albums for manual review. Defaults to false; candidates have no target media identity and must not be used for automatic download.", "name": "Human-readable name of the site, storage item, subscription, or rule group.", @@ -1266,7 +1267,8 @@ def build_api_mcp_input_schema( "title": operation_id, "description": ( f"{summary} Method: {route.method}. Path: {route.path}. " - f"Effect: {spec.effect.value}.{collection_guidance}" + f"Effect: {spec.effect.value}. Identity scope: {spec.identity_scope.value}. " + f"Required role: {spec.required_role.value}.{collection_guidance}" ), "properties": properties, "required": required, diff --git a/app/agent/policy/resources/api_mcp_schema.json b/app/agent/policy/resources/api_mcp_schema.json index d11aa8882..2dce0c4bf 100644 --- a/app/agent/policy/resources/api_mcp_schema.json +++ b/app/agent/policy/resources/api_mcp_schema.json @@ -2650,6 +2650,18 @@ "description": "Minimum source file size accepted by manual transfer, in bytes.", "title": "Min Filesize" }, + "music_cue_enable": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "description": "Optional per-subscription CUE organization setting; omit to inherit the system default.", + "title": "Music Cue Enable" + }, "music_release_regions": { "anyOf": [ { @@ -6571,7 +6583,7 @@ "oneOf": [ { "additionalProperties": false, - "description": "Read the complete custom media-recognition identifier list. Method: GET. Path: /api/v1/system/identifiers. Effect: safe_read.", + "description": "Read the complete custom media-recognition identifier list. Method: GET. Path: /api/v1/system/identifiers. Effect: safe_read. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "config.identifiers.get", @@ -6587,7 +6599,7 @@ }, { "additionalProperties": false, - "description": "Replace the complete custom media-recognition identifier list. Method: POST. Path: /api/v1/system/identifiers. Effect: reversible_write.", + "description": "Replace the complete custom media-recognition identifier list. Method: POST. Path: /api/v1/system/identifiers. Effect: reversible_write. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/CustomIdentifiersUpdateRequest", @@ -6608,7 +6620,7 @@ }, { "additionalProperties": false, - "description": "Read one explicitly public system setting by exact key. Method: GET. Path: /api/v1/system/setting/public/{key}. Effect: safe_read.", + "description": "Read one explicitly public system setting by exact key. Method: GET. Path: /api/v1/system/setting/public/{key}. Effect: safe_read. Identity scope: bound_user. Required role: user.", "properties": { "operation_id": { "const": "config.public.get", @@ -6640,7 +6652,7 @@ }, { "additionalProperties": false, - "description": "Read one exact system setting contract, current value, and revision. Method: GET. Path: /api/v1/system/settings/describe/{setting_key}. Effect: safe_read.", + "description": "Read one exact system setting contract, current value, and revision. Method: GET. Path: /api/v1/system/settings/describe/{setting_key}. Effect: safe_read. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "config.system.describe", @@ -6691,7 +6703,7 @@ }, { "additionalProperties": false, - "description": "Discover registered system settings or read one exact setting. Method: GET. Path: /api/v1/system/settings. Effect: safe_read.", + "description": "Discover registered system settings or read one exact setting. Method: GET. Path: /api/v1/system/settings. Effect: safe_read. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "config.system.get", @@ -6769,7 +6781,7 @@ }, { "additionalProperties": false, - "description": "List compact machine-readable contracts for registered system settings. Method: GET. Path: /api/v1/system/settings/catalog. Effect: safe_read.", + "description": "List compact machine-readable contracts for registered system settings. Method: GET. Path: /api/v1/system/settings/catalog. Effect: safe_read. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "config.system.list", @@ -6844,7 +6856,7 @@ }, { "additionalProperties": false, - "description": "Update one exact registered system setting. Method: POST. Path: /api/v1/system/settings. Effect: reversible_write.", + "description": "Update one exact registered system setting. Method: POST. Path: /api/v1/system/settings. Effect: reversible_write. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/SystemSettingsUpdateRequest", @@ -6865,7 +6877,7 @@ }, { "additionalProperties": false, - "description": "Read current-user feature flags, runtime capabilities, and effective permissions. Method: GET. Path: /api/v1/system/global/user. Effect: safe_read.", + "description": "Read current-user feature flags, runtime capabilities, and effective permissions. Method: GET. Path: /api/v1/system/global/user. Effect: safe_read. Identity scope: bound_user. Required role: user.", "properties": { "operation_id": { "const": "config.user.get", @@ -6881,7 +6893,7 @@ }, { "additionalProperties": false, - "description": "Read the current host CPU utilization percentage. Method: GET. Path: /api/v1/dashboard/cpu. Effect: safe_read.", + "description": "Read the current host CPU utilization percentage. Method: GET. Path: /api/v1/dashboard/cpu. Effect: safe_read. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "dashboard.cpu", @@ -6897,7 +6909,7 @@ }, { "additionalProperties": false, - "description": "Read aggregate downloader task counts, speeds, and free-space information. Method: GET. Path: /api/v1/dashboard/downloader. Effect: safe_read.", + "description": "Read aggregate downloader task counts, speeds, and free-space information. Method: GET. Path: /api/v1/dashboard/downloader. Effect: safe_read. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "dashboard.downloader", @@ -6932,7 +6944,7 @@ }, { "additionalProperties": false, - "description": "Read aggregate movie, TV, episode, and music library counts. Method: GET. Path: /api/v1/dashboard/statistic. Effect: safe_read.", + "description": "Read aggregate movie, TV, episode, and music library counts. Method: GET. Path: /api/v1/dashboard/statistic. Effect: safe_read. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "dashboard.media.statistics", @@ -6967,7 +6979,7 @@ }, { "additionalProperties": false, - "description": "Read current MoviePilot process and host memory utilization. Method: GET. Path: /api/v1/dashboard/memory. Effect: safe_read.", + "description": "Read current MoviePilot process and host memory utilization. Method: GET. Path: /api/v1/dashboard/memory. Effect: safe_read. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "dashboard.memory", @@ -6983,7 +6995,7 @@ }, { "additionalProperties": false, - "description": "Read the current host network receive and transmit counters. Method: GET. Path: /api/v1/dashboard/network. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", + "description": "Read the current host network receive and transmit counters. Method: GET. Path: /api/v1/dashboard/network. Effect: safe_read. Identity scope: admin. Required role: system_admin. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "dashboard.network", @@ -7039,7 +7051,7 @@ }, { "additionalProperties": false, - "description": "List host processes visible to the MoviePilot runtime. Method: GET. Path: /api/v1/dashboard/processes. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", + "description": "List host processes visible to the MoviePilot runtime. Method: GET. Path: /api/v1/dashboard/processes. Effect: safe_read. Identity scope: admin. Required role: system_admin. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "dashboard.processes", @@ -7095,7 +7107,7 @@ }, { "additionalProperties": false, - "description": "Read local filesystem capacity and free-space information. Method: GET. Path: /api/v1/dashboard/storage. Effect: safe_read.", + "description": "Read local filesystem capacity and free-space information. Method: GET. Path: /api/v1/dashboard/storage. Effect: safe_read. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "dashboard.storage", @@ -7111,7 +7123,7 @@ }, { "additionalProperties": false, - "description": "Read MoviePilot host, runtime, platform, and uptime summary information. Method: GET. Path: /api/v1/dashboard/system. Effect: safe_read.", + "description": "Read MoviePilot host, runtime, platform, and uptime summary information. Method: GET. Path: /api/v1/dashboard/system. Effect: safe_read. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "dashboard.system", @@ -7127,7 +7139,7 @@ }, { "additionalProperties": false, - "description": "Read aggregate file-transfer counts grouped by time period. Method: GET. Path: /api/v1/dashboard/transfer. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", + "description": "Read aggregate file-transfer counts grouped by time period. Method: GET. Path: /api/v1/dashboard/transfer. Effect: safe_read. Identity scope: admin. Required role: system_admin. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "dashboard.transfer.statistics", @@ -7196,7 +7208,7 @@ }, { "additionalProperties": false, - "description": "Create, verify, and atomically publish a managed database backup. Method: POST. Path: /api/v1/system/database/backups. Effect: external_side_effect.", + "description": "Create, verify, and atomically publish a managed database backup. Method: POST. Path: /api/v1/system/database/backups. Effect: external_side_effect. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "database.backups.create", @@ -7212,7 +7224,7 @@ }, { "additionalProperties": false, - "description": "Delete one exact managed database backup artifact. Method: DELETE. Path: /api/v1/system/database/backups/{name}. Effect: destructive_write.", + "description": "Delete one exact managed database backup artifact. Method: DELETE. Path: /api/v1/system/database/backups/{name}. Effect: destructive_write. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "database.backups.delete", @@ -7244,7 +7256,7 @@ }, { "additionalProperties": false, - "description": "List managed database backup artifacts without exposing host paths. Method: GET. Path: /api/v1/system/database/backups. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", + "description": "List managed database backup artifacts without exposing host paths. Method: GET. Path: /api/v1/system/database/backups. Effect: safe_read. Identity scope: admin. Required role: system_admin. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "database.backups.list", @@ -7300,7 +7312,7 @@ }, { "additionalProperties": false, - "description": "Verify the integrity of one exact managed database backup artifact. Method: POST. Path: /api/v1/system/database/backups/{name}/verify. Effect: safe_read.", + "description": "Verify the integrity of one exact managed database backup artifact. Method: POST. Path: /api/v1/system/database/backups/{name}/verify. Effect: safe_read. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "database.backups.verify", @@ -7332,7 +7344,7 @@ }, { "additionalProperties": false, - "description": "Submit one torrent to MoviePilot's normal download workflow. Method: POST. Path: /api/v1/download/add. Effect: external_side_effect.", + "description": "Submit one torrent to MoviePilot's normal download workflow. Method: POST. Path: /api/v1/download/add. Effect: external_side_effect. Identity scope: bound_user. Required role: user.", "properties": { "body": { "$ref": "#/$defs/Body_add_api_v1_download_add_post", @@ -7353,7 +7365,7 @@ }, { "additionalProperties": false, - "description": "Submit one artist-wide music torrent to MoviePilot's collection download workflow. Method: POST. Path: /api/v1/download/artist-collection. Effect: external_side_effect.", + "description": "Submit one artist-wide music torrent to MoviePilot's collection download workflow. Method: POST. Path: /api/v1/download/artist-collection. Effect: external_side_effect. Identity scope: bound_user. Required role: user.", "properties": { "body": { "$ref": "#/$defs/Body_download_artist_collection_api_v1_download_artist_collection_post", @@ -7374,7 +7386,7 @@ }, { "additionalProperties": false, - "description": "List enabled downloader instance names and provider types without credentials. Method: GET. Path: /api/v1/download/clients. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", + "description": "List enabled downloader instance names and provider types without credentials. Method: GET. Path: /api/v1/download/clients. Effect: safe_read. Identity scope: bound_user. Required role: user. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "download.clients", @@ -7430,7 +7442,7 @@ }, { "additionalProperties": false, - "description": "Delete one MoviePilot download-history record. Method: DELETE. Path: /api/v1/history/download. Effect: destructive_write.", + "description": "Delete one MoviePilot download-history record. Method: DELETE. Path: /api/v1/history/download. Effect: destructive_write. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/DownloadHistory-Input", @@ -7451,7 +7463,7 @@ }, { "additionalProperties": false, - "description": "Page MoviePilot download-history records in reverse chronological order. Method: GET. Path: /api/v1/history/download. Effect: safe_read. Collection response: data remains a list and the endpoint's documented pagination or limit defaults remain in effect. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count-only request, use the smallest valid page and read collection.total_count; do not query the database merely because item data is truncated.", + "description": "Page MoviePilot download-history records in reverse chronological order. Method: GET. Path: /api/v1/history/download. Effect: safe_read. Identity scope: bound_user. Required role: user. Collection response: data remains a list and the endpoint's documented pagination or limit defaults remain in effect. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count-only request, use the smallest valid page and read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "download.history.list", @@ -7506,7 +7518,7 @@ }, { "additionalProperties": false, - "description": "List configured downloader save-path URIs that may be passed to download.add. Each item's non-empty name is also accepted as save_path. Method: GET. Path: /api/v1/download/paths. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", + "description": "List configured downloader save-path URIs that may be passed to download.add. Each item's non-empty name is also accepted as save_path. Method: GET. Path: /api/v1/download/paths. Effect: safe_read. Identity scope: bound_user. Required role: user. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "download.paths", @@ -7562,7 +7574,7 @@ }, { "additionalProperties": false, - "description": "List currently downloading MoviePilot tasks with their canonical media context. Method: GET. Path: /api/v1/download/. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", + "description": "List currently downloading MoviePilot tasks with their canonical media context. Method: GET. Path: /api/v1/download/. Effect: safe_read. Identity scope: bound_user. Required role: user. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "download.tasks.active", @@ -7630,7 +7642,7 @@ }, { "additionalProperties": false, - "description": "List built-in torrent filter rules. Method: GET. Path: /api/v1/rule/builtin. Effect: safe_read.", + "description": "List built-in torrent filter rules. Method: GET. Path: /api/v1/rule/builtin. Effect: safe_read. Identity scope: bound_user. Required role: user.", "properties": { "operation_id": { "const": "filter.builtin", @@ -7668,7 +7680,7 @@ }, { "additionalProperties": false, - "description": "List user-defined torrent filter rules. Method: GET. Path: /api/v1/rule/custom. Effect: safe_read.", + "description": "List user-defined torrent filter rules. Method: GET. Path: /api/v1/rule/custom. Effect: safe_read. Identity scope: bound_user. Required role: user.", "properties": { "operation_id": { "const": "filter.custom", @@ -7712,7 +7724,7 @@ }, { "additionalProperties": false, - "description": "Create one user-defined torrent filter rule. Method: POST. Path: /api/v1/rule/custom. Effect: reversible_write.", + "description": "Create one user-defined torrent filter rule. Method: POST. Path: /api/v1/rule/custom. Effect: reversible_write. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/CustomFilterRuleCreateRequest", @@ -7733,7 +7745,7 @@ }, { "additionalProperties": false, - "description": "Delete one user-defined torrent filter rule. Method: DELETE. Path: /api/v1/rule/custom/{rule_id}. Effect: destructive_write.", + "description": "Delete one user-defined torrent filter rule. Method: DELETE. Path: /api/v1/rule/custom/{rule_id}. Effect: destructive_write. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "filter.custom.delete", @@ -7765,7 +7777,7 @@ }, { "additionalProperties": false, - "description": "Update one user-defined torrent filter rule. Method: PUT. Path: /api/v1/rule/custom/{rule_id}. Effect: reversible_write.", + "description": "Update one user-defined torrent filter rule. Method: PUT. Path: /api/v1/rule/custom/{rule_id}. Effect: reversible_write. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/CustomFilterRuleUpdateRequest", @@ -7802,7 +7814,7 @@ }, { "additionalProperties": false, - "description": "Create one named filter-rule group. Method: POST. Path: /api/v1/rule/groups. Effect: reversible_write.", + "description": "Create one named filter-rule group. Method: POST. Path: /api/v1/rule/groups. Effect: reversible_write. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/FilterRuleGroupCreateRequest", @@ -7823,7 +7835,7 @@ }, { "additionalProperties": false, - "description": "Delete one named filter-rule group. Method: DELETE. Path: /api/v1/rule/groups/{name}. Effect: destructive_write.", + "description": "Delete one named filter-rule group. Method: DELETE. Path: /api/v1/rule/groups/{name}. Effect: destructive_write. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "filter.group.delete", @@ -7855,7 +7867,7 @@ }, { "additionalProperties": false, - "description": "Update or rename one named filter-rule group. Method: PUT. Path: /api/v1/rule/groups/{name}. Effect: reversible_write.", + "description": "Update or rename one named filter-rule group. Method: PUT. Path: /api/v1/rule/groups/{name}. Effect: reversible_write. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/FilterRuleGroupUpdateRequest", @@ -7892,7 +7904,7 @@ }, { "additionalProperties": false, - "description": "List named filter-rule groups. Method: GET. Path: /api/v1/rule/groups. Effect: safe_read.", + "description": "List named filter-rule groups. Method: GET. Path: /api/v1/rule/groups. Effect: safe_read. Identity scope: bound_user. Required role: user.", "properties": { "operation_id": { "const": "filter.groups", @@ -7936,7 +7948,7 @@ }, { "additionalProperties": false, - "description": "Test one title and optional subtitle against an exact named filter-rule group. Method: GET. Path: /api/v1/system/ruletest. Effect: external_side_effect.", + "description": "Test one title and optional subtitle against an exact named filter-rule group. Method: GET. Path: /api/v1/system/ruletest. Effect: external_side_effect. Identity scope: bound_user. Required role: user.", "properties": { "operation_id": { "const": "filter.test", @@ -7986,7 +7998,7 @@ }, { "additionalProperties": false, - "description": "Check configured media servers for one canonical media identity. Method: GET. Path: /api/v1/mediaserver/exists. Effect: safe_read.", + "description": "Check configured media servers for one canonical media identity. Method: GET. Path: /api/v1/mediaserver/exists. Effect: safe_read. Identity scope: bound_user. Required role: user.", "properties": { "operation_id": { "const": "library.exists", @@ -8081,7 +8093,7 @@ }, { "additionalProperties": false, - "description": "List recently added items from one configured media-server instance for the current user. Method: GET. Path: /api/v1/mediaserver/latest. Effect: safe_read. Collection response: data remains a list and successful gateway output adds collection.result_count. collection.total_count is omitted when the endpoint or its upstream source does not expose a total.", + "description": "List recently added items from one configured media-server instance for the current user. Method: GET. Path: /api/v1/mediaserver/latest. Effect: safe_read. Identity scope: bound_user. Required role: user. Collection response: data remains a list and successful gateway output adds collection.result_count. collection.total_count is omitted when the endpoint or its upstream source does not expose a total.", "properties": { "operation_id": { "const": "library.latest", @@ -8132,7 +8144,7 @@ }, { "additionalProperties": false, - "description": "Clear the complete administrator-only TMDB recognition cache. Method: DELETE. Path: /api/v1/tmdb/cache. Effect: destructive_write.", + "description": "Clear the complete administrator-only TMDB recognition cache. Method: DELETE. Path: /api/v1/tmdb/cache. Effect: destructive_write. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "media.cache.clear", @@ -8148,7 +8160,7 @@ }, { "additionalProperties": false, - "description": "Delete one administrator-only TMDB recognition-cache entry by exact key. Method: DELETE. Path: /api/v1/tmdb/cache/{cache_key}. Effect: destructive_write.", + "description": "Delete one administrator-only TMDB recognition-cache entry by exact key. Method: DELETE. Path: /api/v1/tmdb/cache/{cache_key}. Effect: destructive_write. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "media.cache.delete", @@ -8180,7 +8192,7 @@ }, { "additionalProperties": false, - "description": "Inspect the administrator-only TMDB recognition cache and summary counts. Method: GET. Path: /api/v1/tmdb/cache. Effect: safe_read.", + "description": "Inspect the administrator-only TMDB recognition cache and summary counts. Method: GET. Path: /api/v1/tmdb/cache. Effect: safe_read. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "media.cache.get", @@ -8196,7 +8208,7 @@ }, { "additionalProperties": false, - "description": "Read the media-classification field catalog and source capabilities. Method: GET. Path: /api/v1/media/classification/fields. Effect: safe_read.", + "description": "Read the media-classification field catalog and source capabilities. Method: GET. Path: /api/v1/media/classification/fields. Effect: safe_read. Identity scope: bound_user. Required role: user.", "properties": { "operation_id": { "const": "media.classification.fields", @@ -8212,7 +8224,7 @@ }, { "additionalProperties": false, - "description": "Read the active automatic media-classification policy. Method: GET. Path: /api/v1/media/classification/policy. Effect: safe_read.", + "description": "Read the active automatic media-classification policy. Method: GET. Path: /api/v1/media/classification/policy. Effect: safe_read. Identity scope: bound_user. Required role: user.", "properties": { "operation_id": { "const": "media.classification.policy.get", @@ -8247,7 +8259,7 @@ }, { "additionalProperties": false, - "description": "Read the bounded history of published media-classification policies. Method: GET. Path: /api/v1/media/classification/history. Effect: safe_read.", + "description": "Read the bounded history of published media-classification policies. Method: GET. Path: /api/v1/media/classification/history. Effect: safe_read. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "media.classification.policy.history", @@ -8263,7 +8275,7 @@ }, { "additionalProperties": false, - "description": "Estimate how an unpublished media-classification policy changes bounded recent samples. Method: POST. Path: /api/v1/media/classification/impact. Effect: safe_read.", + "description": "Estimate how an unpublished media-classification policy changes bounded recent samples. Method: POST. Path: /api/v1/media/classification/impact. Effect: safe_read. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/ClassificationImpactRequest", @@ -8284,7 +8296,7 @@ }, { "additionalProperties": false, - "description": "Preview automatic media classification for selected media or normalized facts. Method: POST. Path: /api/v1/media/classification/preview. Effect: safe_read.", + "description": "Preview automatic media classification for selected media or normalized facts. Method: POST. Path: /api/v1/media/classification/preview. Effect: safe_read. Identity scope: bound_user. Required role: user.", "properties": { "body": { "$ref": "#/$defs/ClassificationPreviewRequest", @@ -8305,7 +8317,7 @@ }, { "additionalProperties": false, - "description": "Publish a selected historical media-classification policy as a new revision. Method: POST. Path: /api/v1/media/classification/rollback/{revision}. Effect: reversible_write.", + "description": "Publish a selected historical media-classification policy as a new revision. Method: POST. Path: /api/v1/media/classification/rollback/{revision}. Effect: reversible_write. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/ClassificationPolicyRollbackRequest", @@ -8342,7 +8354,7 @@ }, { "additionalProperties": false, - "description": "Validate and publish a complete automatic media-classification policy. Method: PUT. Path: /api/v1/media/classification/policy. Effect: reversible_write.", + "description": "Validate and publish a complete automatic media-classification policy. Method: PUT. Path: /api/v1/media/classification/policy. Effect: reversible_write. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/ClassificationPolicyPublishRequest", @@ -8363,7 +8375,7 @@ }, { "additionalProperties": false, - "description": "Validate an unpublished automatic media-classification policy without saving it. Method: POST. Path: /api/v1/media/classification/validate. Effect: safe_read.", + "description": "Validate an unpublished automatic media-classification policy without saving it. Method: POST. Path: /api/v1/media/classification/validate. Effect: safe_read. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/ClassificationPolicyValidateRequest", @@ -8384,7 +8396,7 @@ }, { "additionalProperties": false, - "description": "Read canonical media details from one selected metadata source. Method: GET. Path: /api/v1/media/{media_id}. Effect: safe_read.", + "description": "Read canonical media details from one selected metadata source. Method: GET. Path: /api/v1/media/{media_id}. Effect: safe_read. Identity scope: public. Required role: user.", "properties": { "operation_id": { "const": "media.detail", @@ -8437,7 +8449,7 @@ }, { "additionalProperties": false, - "description": "List seasons defined by one exact TMDB episode-group identity. Method: GET. Path: /api/v1/media/group/seasons/{episode_group}. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", + "description": "List seasons defined by one exact TMDB episode-group identity. Method: GET. Path: /api/v1/media/group/seasons/{episode_group}. Effect: safe_read. Identity scope: public. Required role: user. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "media.episode_group.seasons", @@ -8509,7 +8521,7 @@ }, { "additionalProperties": false, - "description": "List alternate TMDB episode groups available for one TV media identity. Method: GET. Path: /api/v1/media/groups/{tmdbid}. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", + "description": "List alternate TMDB episode groups available for one TV media identity. Method: GET. Path: /api/v1/media/groups/{tmdbid}. Effect: safe_read. Identity scope: public. Required role: user. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "media.episode_groups", @@ -8581,7 +8593,7 @@ }, { "additionalProperties": false, - "description": "Read TMDB episode release information for one season. Method: GET. Path: /api/v1/tmdb/{tmdbid}/{season}. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", + "description": "Read TMDB episode release information for one season. Method: GET. Path: /api/v1/tmdb/{tmdbid}/{season}. Effect: safe_read. Identity scope: public. Required role: user. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "media.episode_schedule", @@ -8671,7 +8683,7 @@ }, { "additionalProperties": false, - "description": "Read one person's credits from the selected metadata source. Method: GET. Path: /api/v1/{source}/person/credits/{person_id}. Effect: safe_read. Collection response: data remains a list and successful gateway output adds collection.result_count. collection.total_count is omitted when the endpoint or its upstream source does not expose a total.", + "description": "Read one person's credits from the selected metadata source. Method: GET. Path: /api/v1/{source}/person/credits/{person_id}. Effect: safe_read. Identity scope: public. Required role: user. Collection response: data remains a list and successful gateway output adds collection.result_count. collection.total_count is omitted when the endpoint or its upstream source does not expose a total.", "properties": { "operation_id": { "const": "media.person.credits", @@ -8739,7 +8751,7 @@ }, { "additionalProperties": false, - "description": "Search people and music artists across selected metadata sources. Method: GET. Path: /api/v1/media/search. Effect: safe_read. Collection response: data remains a list and successful gateway output adds collection.result_count. collection.total_count is omitted when the endpoint or its upstream source does not expose a total.", + "description": "Search people and music artists across selected metadata sources. Method: GET. Path: /api/v1/media/search. Effect: safe_read. Identity scope: public. Required role: user. Collection response: data remains a list and successful gateway output adds collection.result_count. collection.total_count is omitted when the endpoint or its upstream source does not expose a total.", "properties": { "operation_id": { "const": "media.person.search", @@ -8821,7 +8833,7 @@ }, { "additionalProperties": false, - "description": "Recognize media identity from a title, subtitle, or custom rule context. Method: GET. Path: /api/v1/media/recognize. Effect: safe_read.", + "description": "Recognize media identity from a title, subtitle, or custom rule context. Method: GET. Path: /api/v1/media/recognize. Effect: safe_read. Identity scope: bound_user. Required role: user.", "properties": { "operation_id": { "const": "media.recognize", @@ -8889,7 +8901,7 @@ }, { "additionalProperties": false, - "description": "Recognize canonical media identity from one exact filename and optional path context. Method: GET. Path: /api/v1/media/recognize_file. Effect: safe_read.", + "description": "Recognize canonical media identity from one exact filename and optional path context. Method: GET. Path: /api/v1/media/recognize_file. Effect: safe_read. Identity scope: bound_user. Required role: user.", "properties": { "operation_id": { "const": "media.recognize_file", @@ -8933,7 +8945,7 @@ }, { "additionalProperties": false, - "description": "Generate or refresh metadata for one storage item. Method: POST. Path: /api/v1/media/scrape/{storage}. Effect: external_side_effect.", + "description": "Generate or refresh metadata for one storage item. Method: POST. Path: /api/v1/media/scrape/{storage}. Effect: external_side_effect. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/FileItem-Input", @@ -9044,7 +9056,7 @@ }, { "additionalProperties": false, - "description": "Search canonical media across selected metadata sources. Method: GET. Path: /api/v1/media/search. Effect: safe_read. Collection response: data remains a list and successful gateway output adds collection.result_count. collection.total_count is omitted when the endpoint or its upstream source does not expose a total.", + "description": "Search canonical media across selected metadata sources. Method: GET. Path: /api/v1/media/search. Effect: safe_read. Identity scope: public. Required role: user. Collection response: data remains a list and successful gateway output adds collection.result_count. collection.total_count is omitted when the endpoint or its upstream source does not expose a total.", "properties": { "operation_id": { "const": "media.search", @@ -9133,7 +9145,7 @@ }, { "additionalProperties": false, - "description": "List seasons for one exact media identity or a title-and-year fallback. Method: GET. Path: /api/v1/media/seasons. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", + "description": "List seasons for one exact media identity or a title-and-year fallback. Method: GET. Path: /api/v1/media/seasons. Effect: safe_read. Identity scope: public. Required role: user. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "media.seasons", @@ -9235,7 +9247,7 @@ }, { "additionalProperties": false, - "description": "List metadata sources currently registered for MoviePilot media operations. Method: GET. Path: /api/v1/media/source. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", + "description": "List metadata sources currently registered for MoviePilot media operations. Method: GET. Path: /api/v1/media/source. Effect: safe_read. Identity scope: bound_user. Required role: user. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "media.sources", @@ -9291,7 +9303,7 @@ }, { "additionalProperties": false, - "description": "Read one album's details, tracks, releases, and aligned artist names and IDs. Method: GET. Path: /api/v1/music/album/{album_id}. Effect: safe_read.", + "description": "Read one album's details, tracks, releases, and aligned artist names and IDs. Method: GET. Path: /api/v1/music/album/{album_id}. Effect: safe_read. Identity scope: public. Required role: user.", "properties": { "operation_id": { "const": "music.album.get", @@ -9347,7 +9359,7 @@ }, { "additionalProperties": false, - "description": "Browse albums related to one source-native album identity. Method: GET. Path: /api/v1/music/album/{album_id}/related. Effect: safe_read. Collection response: data remains a list and successful gateway output adds collection.result_count. collection.total_count is omitted when the endpoint or its upstream source does not expose a total.", + "description": "Browse albums related to one source-native album identity. Method: GET. Path: /api/v1/music/album/{album_id}/related. Effect: safe_read. Identity scope: public. Required role: user. Collection response: data remains a list and successful gateway output adds collection.result_count. collection.total_count is omitted when the endpoint or its upstream source does not expose a total.", "properties": { "operation_id": { "const": "music.album.related", @@ -9404,7 +9416,7 @@ }, { "additionalProperties": false, - "description": "Browse one artist's albums, singles, EPs, or another exact release-group type. Method: GET. Path: /api/v1/music/artist/{artist_id}/albums. Effect: safe_read. Collection response: data remains a list and successful gateway output adds collection.result_count. collection.total_count is omitted when the endpoint or its upstream source does not expose a total.", + "description": "Browse one artist's albums, singles, EPs, or another exact release-group type. Method: GET. Path: /api/v1/music/artist/{artist_id}/albums. Effect: safe_read. Identity scope: public. Required role: user. Collection response: data remains a list and successful gateway output adds collection.result_count. collection.total_count is omitted when the endpoint or its upstream source does not expose a total.", "properties": { "operation_id": { "const": "music.artist.albums", @@ -9481,7 +9493,7 @@ }, { "additionalProperties": false, - "description": "Read one artist's canonical details from the selected music metadata source. Method: GET. Path: /api/v1/music/artist/{artist_id}. Effect: safe_read.", + "description": "Read one artist's canonical details from the selected music metadata source. Method: GET. Path: /api/v1/music/artist/{artist_id}. Effect: safe_read. Identity scope: public. Required role: user.", "properties": { "operation_id": { "const": "music.artist.get", @@ -9524,7 +9536,7 @@ }, { "additionalProperties": false, - "description": "Browse artists related to one source-native artist identity. Method: GET. Path: /api/v1/music/artist/{artist_id}/related. Effect: safe_read. Collection response: data remains a list and successful gateway output adds collection.result_count. collection.total_count is omitted when the endpoint or its upstream source does not expose a total.", + "description": "Browse artists related to one source-native artist identity. Method: GET. Path: /api/v1/music/artist/{artist_id}/related. Effect: safe_read. Identity scope: public. Required role: user. Collection response: data remains a list and successful gateway output adds collection.result_count. collection.total_count is omitted when the endpoint or its upstream source does not expose a total.", "properties": { "operation_id": { "const": "music.artist.related", @@ -9581,7 +9593,7 @@ }, { "additionalProperties": false, - "description": "Clear the complete administrator-only MusicBrainz recognition cache. Method: DELETE. Path: /api/v1/music/cache. Effect: destructive_write.", + "description": "Clear the complete administrator-only MusicBrainz recognition cache. Method: DELETE. Path: /api/v1/music/cache. Effect: destructive_write. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "music.cache.clear", @@ -9597,7 +9609,7 @@ }, { "additionalProperties": false, - "description": "Delete one administrator-only MusicBrainz recognition-cache entry by exact key. Method: DELETE. Path: /api/v1/music/cache/{cache_key}. Effect: destructive_write.", + "description": "Delete one administrator-only MusicBrainz recognition-cache entry by exact key. Method: DELETE. Path: /api/v1/music/cache/{cache_key}. Effect: destructive_write. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "music.cache.delete", @@ -9629,7 +9641,7 @@ }, { "additionalProperties": false, - "description": "Inspect the administrator-only MusicBrainz recognition cache and summary counts. Method: GET. Path: /api/v1/music/cache. Effect: safe_read.", + "description": "Inspect the administrator-only MusicBrainz recognition cache and summary counts. Method: GET. Path: /api/v1/music/cache. Effect: safe_read. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "music.cache.get", @@ -9645,7 +9657,7 @@ }, { "additionalProperties": false, - "description": "Browse MusicBrainz charts or fresh releases, or Douban Music tag categories. Method: GET. Path: /api/v1/music/explore. Effect: safe_read. Collection response: data remains a list and successful gateway output adds collection.result_count. collection.total_count is omitted when the endpoint or its upstream source does not expose a total.", + "description": "Browse MusicBrainz charts or fresh releases, or Douban Music tag categories. Method: GET. Path: /api/v1/music/explore. Effect: safe_read. Identity scope: public. Required role: user. Collection response: data remains a list and successful gateway output adds collection.result_count. collection.total_count is omitted when the endpoint or its upstream source does not expose a total.", "properties": { "operation_id": { "const": "music.explore", @@ -9774,7 +9786,7 @@ }, { "additionalProperties": false, - "description": "Resolve one recording or album from an exact music source and source-native ID. Method: POST. Path: /api/v1/music/recognize. Effect: safe_read.", + "description": "Resolve one recording or album from an exact music source and source-native ID. Method: POST. Path: /api/v1/music/recognize. Effect: safe_read. Identity scope: public. Required role: user.", "properties": { "body": { "$ref": "#/$defs/MusicRecognizeRequest", @@ -9795,7 +9807,7 @@ }, { "additionalProperties": false, - "description": "Inspect the runtime capabilities exposed by installed plugins. Method: GET. Path: /api/v1/plugin/runtime/capabilities. Effect: safe_read.", + "description": "Inspect the runtime capabilities exposed by installed plugins. Method: GET. Path: /api/v1/plugin/runtime/capabilities. Effect: safe_read. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "plugin.capabilities", @@ -9830,7 +9842,7 @@ }, { "additionalProperties": false, - "description": "Create a configurable clone of one installed plugin; leave suffix empty to let the server allocate the next free instance ID, and reusing the suffix of a disabled clone re-enables that clone together with its stored configuration. Method: POST. Path: /api/v1/plugin/clone/{plugin_id}. Effect: external_side_effect.", + "description": "Create a configurable clone of one installed plugin; leave suffix empty to let the server allocate the next free instance ID, and reusing the suffix of a disabled clone re-enables that clone together with its stored configuration. Method: POST. Path: /api/v1/plugin/clone/{plugin_id}. Effect: external_side_effect. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/PluginCloneRequest", @@ -9867,7 +9879,7 @@ }, { "additionalProperties": false, - "description": "List one plugin's disabled clones whose configuration is still stored and can be brought back by creating a clone with the same suffix. Method: GET. Path: /api/v1/plugin/clone/{plugin_id}/restorable. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", + "description": "List one plugin's disabled clones whose configuration is still stored and can be brought back by creating a clone with the same suffix. Method: GET. Path: /api/v1/plugin/clone/{plugin_id}/restorable. Effect: safe_read. Identity scope: admin. Required role: system_admin. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "plugin.clone.restorable", @@ -9939,7 +9951,7 @@ }, { "additionalProperties": false, - "description": "Read one loaded plugin's configuration form and its defaults merged with saved values. Method: GET. Path: /api/v1/plugin/form/{plugin_id}. Effect: safe_read.", + "description": "Read one loaded plugin's configuration form and its defaults merged with saved values. Method: GET. Path: /api/v1/plugin/form/{plugin_id}. Effect: safe_read. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "plugin.config.get", @@ -9971,7 +9983,7 @@ }, { "additionalProperties": false, - "description": "Replace one installed plugin's complete configuration and apply it immediately. Method: PUT. Path: /api/v1/plugin/{plugin_id}. Effect: reversible_write.", + "description": "Replace one installed plugin's complete configuration and apply it immediately. Method: PUT. Path: /api/v1/plugin/{plugin_id}. Effect: reversible_write. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "additionalProperties": true, @@ -10011,7 +10023,7 @@ }, { "additionalProperties": false, - "description": "Read a bounded preview of one plugin's persisted data. Method: GET. Path: /api/v1/plugin/runtime/{plugin_id}/data. Effect: safe_read.", + "description": "Read a bounded preview of one plugin's persisted data. Method: GET. Path: /api/v1/plugin/runtime/{plugin_id}/data. Effect: safe_read. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "plugin.data", @@ -10074,7 +10086,7 @@ }, { "additionalProperties": false, - "description": "Clear one plugin instance's default-call-target flag, only if it is the plugin's current default. Method: DELETE. Path: /api/v1/plugin/instances/{plugin_id}/{instance_id}/default_target. Effect: reversible_write.", + "description": "Clear one plugin instance's default-call-target flag, only if it is the plugin's current default. Method: DELETE. Path: /api/v1/plugin/instances/{plugin_id}/{instance_id}/default_target. Effect: reversible_write. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "plugin.default_target.clear", @@ -10112,7 +10124,7 @@ }, { "additionalProperties": false, - "description": "Set one plugin instance as the plugin's default call target, automatically clearing any previous default. Method: PUT. Path: /api/v1/plugin/instances/{plugin_id}/{instance_id}/default_target. Effect: reversible_write.", + "description": "Set one plugin instance as the plugin's default call target, automatically clearing any previous default. Method: PUT. Path: /api/v1/plugin/instances/{plugin_id}/{instance_id}/default_target. Effect: reversible_write. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "plugin.default_target.set", @@ -10150,7 +10162,7 @@ }, { "additionalProperties": false, - "description": "Create one named plugin folder. Method: POST. Path: /api/v1/plugin/folders/{folder_name}. Effect: reversible_write.", + "description": "Create one named plugin folder. Method: POST. Path: /api/v1/plugin/folders/{folder_name}. Effect: reversible_write. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "plugin.folder.create", @@ -10182,7 +10194,7 @@ }, { "additionalProperties": false, - "description": "Delete one named plugin folder without uninstalling its plugins. Method: DELETE. Path: /api/v1/plugin/folders/{folder_name}. Effect: destructive_write.", + "description": "Delete one named plugin folder without uninstalling its plugins. Method: DELETE. Path: /api/v1/plugin/folders/{folder_name}. Effect: destructive_write. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "plugin.folder.delete", @@ -10214,7 +10226,7 @@ }, { "additionalProperties": false, - "description": "Move one installed plugin into one named folder and remove its other folder assignments. Method: PUT. Path: /api/v1/plugin/folders/{folder_name}/plugins/{plugin_id}. Effect: reversible_write.", + "description": "Move one installed plugin into one named folder and remove its other folder assignments. Method: PUT. Path: /api/v1/plugin/folders/{folder_name}/plugins/{plugin_id}. Effect: reversible_write. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "plugin.folder.plugin.assign", @@ -10252,7 +10264,7 @@ }, { "additionalProperties": false, - "description": "Remove one installed plugin from one named folder without uninstalling it. Method: DELETE. Path: /api/v1/plugin/folders/{folder_name}/plugins/{plugin_id}. Effect: reversible_write.", + "description": "Remove one installed plugin from one named folder without uninstalling it. Method: DELETE. Path: /api/v1/plugin/folders/{folder_name}/plugins/{plugin_id}. Effect: reversible_write. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "plugin.folder.plugin.remove", @@ -10290,7 +10302,7 @@ }, { "additionalProperties": false, - "description": "Replace the ordered plugin IDs assigned to one named plugin folder. Method: PUT. Path: /api/v1/plugin/folders/{folder_name}/plugins. Effect: reversible_write.", + "description": "Replace the ordered plugin IDs assigned to one named plugin folder. Method: PUT. Path: /api/v1/plugin/folders/{folder_name}/plugins. Effect: reversible_write. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "anyOf": [ @@ -10338,7 +10350,7 @@ }, { "additionalProperties": false, - "description": "Incrementally rename one plugin folder or update its presentation settings. Method: PATCH. Path: /api/v1/plugin/folders/{folder_name}. Effect: reversible_write.", + "description": "Incrementally rename one plugin folder or update its presentation settings. Method: PATCH. Path: /api/v1/plugin/folders/{folder_name}. Effect: reversible_write. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/PluginFolderUpdateRequest", @@ -10375,7 +10387,7 @@ }, { "additionalProperties": false, - "description": "Read the complete administrator plugin-folder grouping configuration. Method: GET. Path: /api/v1/plugin/folders. Effect: safe_read.", + "description": "Read the complete administrator plugin-folder grouping configuration. Method: GET. Path: /api/v1/plugin/folders. Effect: safe_read. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "plugin.folders.get", @@ -10391,7 +10403,7 @@ }, { "additionalProperties": false, - "description": "Replace the complete administrator plugin-folder grouping configuration. Method: POST. Path: /api/v1/plugin/folders. Effect: reversible_write.", + "description": "Replace the complete administrator plugin-folder grouping configuration. Method: POST. Path: /api/v1/plugin/folders. Effect: reversible_write. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/PluginFoldersData", @@ -10412,7 +10424,7 @@ }, { "additionalProperties": false, - "description": "Read marketplace update notes and history for one plugin. Method: GET. Path: /api/v1/plugin/history/{plugin_id}. Effect: safe_read.", + "description": "Read marketplace update notes and history for one plugin. Method: GET. Path: /api/v1/plugin/history/{plugin_id}. Effect: safe_read. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "plugin.history", @@ -10457,7 +10469,7 @@ }, { "additionalProperties": false, - "description": "Install or update one plugin from an approved source. Method: GET. Path: /api/v1/plugin/install/{plugin_id}. Effect: external_side_effect.", + "description": "Install or update one plugin from an approved source. Method: GET. Path: /api/v1/plugin/install/{plugin_id}. Effect: external_side_effect. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "plugin.install", @@ -10534,7 +10546,7 @@ }, { "additionalProperties": false, - "description": "List installed plugins and their runtime status. Method: GET. Path: /api/v1/plugin/. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", + "description": "List installed plugins and their runtime status. Method: GET. Path: /api/v1/plugin/. Effect: safe_read. Identity scope: admin. Required role: system_admin. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "plugin.installed", @@ -10631,7 +10643,7 @@ }, { "additionalProperties": false, - "description": "Permanently delete one plugin instance's selected data: saved configuration, plugin data rows, its own database and its data directory; a clone also loses its instance record. Nothing is deleted unless explicitly selected. Method: POST. Path: /api/v1/plugin/instance/{instance_id}/purge. Effect: destructive_write.", + "description": "Permanently delete one plugin instance's selected data: saved configuration, plugin data rows, its own database and its data directory; a clone also loses its instance record. Nothing is deleted unless explicitly selected. Method: POST. Path: /api/v1/plugin/instance/{instance_id}/purge. Effect: destructive_write. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/PluginInstancePurgeRequest", @@ -10668,7 +10680,7 @@ }, { "additionalProperties": false, - "description": "Enable or disable one plugin instance, host or clone; disabling only stops it running and keeps its configuration for a later re-enable. Method: POST. Path: /api/v1/plugin/instance/{instance_id}/enabled. Effect: reversible_write.", + "description": "Enable or disable one plugin instance, host or clone; disabling only stops it running and keeps its configuration for a later re-enable. Method: POST. Path: /api/v1/plugin/instance/{instance_id}/enabled. Effect: reversible_write. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/PluginInstanceEnabledRequest", @@ -10705,7 +10717,7 @@ }, { "additionalProperties": false, - "description": "Clear one plugin instance's log-level override so it immediately follows the global log level again. Method: DELETE. Path: /api/v1/plugin/loglevel/{plugin_id}/{instance_id}. Effect: reversible_write.", + "description": "Clear one plugin instance's log-level override so it immediately follows the global log level again. Method: DELETE. Path: /api/v1/plugin/loglevel/{plugin_id}/{instance_id}. Effect: reversible_write. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "plugin.loglevel.clear", @@ -10743,7 +10755,7 @@ }, { "additionalProperties": false, - "description": "List one plugin's instances, including the plugin itself, with each instance's configured and effective log level. Method: GET. Path: /api/v1/plugin/loglevel/{plugin_id}. Effect: safe_read.", + "description": "List one plugin's instances, including the plugin itself, with each instance's configured and effective log level. Method: GET. Path: /api/v1/plugin/loglevel/{plugin_id}. Effect: safe_read. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "plugin.loglevel.get", @@ -10775,7 +10787,7 @@ }, { "additionalProperties": false, - "description": "Set one plugin instance's log-level override, taking effect immediately without following the global log level. Method: PUT. Path: /api/v1/plugin/loglevel/{plugin_id}/{instance_id}. Effect: reversible_write.", + "description": "Set one plugin instance's log-level override, taking effect immediately without following the global log level. Method: PUT. Path: /api/v1/plugin/loglevel/{plugin_id}/{instance_id}. Effect: reversible_write. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/PluginInstanceLogLevelUpdateRequest", @@ -10818,7 +10830,7 @@ }, { "additionalProperties": false, - "description": "List plugins available from configured marketplaces. Method: GET. Path: /api/v1/plugin/. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", + "description": "List plugins available from configured marketplaces. Method: GET. Path: /api/v1/plugin/. Effect: safe_read. Identity scope: admin. Required role: system_admin. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "plugin.market", @@ -10915,7 +10927,7 @@ }, { "additionalProperties": false, - "description": "Refresh the configured plugin marketplace repositories from the MoviePilot Wiki. Method: POST. Path: /api/v1/system/setting/PLUGIN_MARKET/sync-wiki. Effect: external_side_effect.", + "description": "Refresh the configured plugin marketplace repositories from the MoviePilot Wiki. Method: POST. Path: /api/v1/system/setting/PLUGIN_MARKET/sync-wiki. Effect: external_side_effect. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "anyOf": [ @@ -10943,7 +10955,7 @@ }, { "additionalProperties": false, - "description": "Read the current aggregate rating for one plugin. Method: GET. Path: /api/v1/plugin/rating/{plugin_id}. Effect: safe_read.", + "description": "Read the current aggregate rating for one plugin. Method: GET. Path: /api/v1/plugin/rating/{plugin_id}. Effect: safe_read. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "plugin.rating", @@ -10975,7 +10987,7 @@ }, { "additionalProperties": false, - "description": "Submit or replace the current user's rating for one plugin. Method: POST. Path: /api/v1/plugin/rating/{plugin_id}. Effect: external_side_effect.", + "description": "Submit or replace the current user's rating for one plugin. Method: POST. Path: /api/v1/plugin/rating/{plugin_id}. Effect: external_side_effect. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/PluginRatingRequest", @@ -11012,7 +11024,7 @@ }, { "additionalProperties": false, - "description": "Read aggregate ratings for a requested plugin set. Method: GET. Path: /api/v1/plugin/rating. Effect: safe_read.", + "description": "Read aggregate ratings for a requested plugin set. Method: GET. Path: /api/v1/plugin/rating. Effect: safe_read. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "plugin.ratings", @@ -11047,7 +11059,7 @@ }, { "additionalProperties": false, - "description": "List available release versions for one plugin source. Method: GET. Path: /api/v1/plugin/releases/{plugin_id}. Effect: safe_read.", + "description": "List available release versions for one plugin source. Method: GET. Path: /api/v1/plugin/releases/{plugin_id}. Effect: safe_read. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "plugin.releases", @@ -11105,7 +11117,7 @@ }, { "additionalProperties": false, - "description": "Reload one installed plugin into the running process. Method: POST. Path: /api/v1/plugin/reload/{plugin_id}. Effect: external_side_effect.", + "description": "Reload one installed plugin into the running process. Method: POST. Path: /api/v1/plugin/reload/{plugin_id}. Effect: external_side_effect. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "plugin.reload", @@ -11137,7 +11149,7 @@ }, { "additionalProperties": false, - "description": "Delete one plugin's saved configuration and data, then restore its default runtime state. Method: GET. Path: /api/v1/plugin/reset/{plugin_id}. Effect: destructive_write.", + "description": "Delete one plugin's saved configuration and data, then restore its default runtime state. Method: GET. Path: /api/v1/plugin/reset/{plugin_id}. Effect: destructive_write. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "plugin.reset", @@ -11169,7 +11181,7 @@ }, { "additionalProperties": false, - "description": "Read plugin runtime convergence, loading, and failure state. Method: GET. Path: /api/v1/plugin/runtime. Effect: safe_read.", + "description": "Read plugin runtime convergence, loading, and failure state. Method: GET. Path: /api/v1/plugin/runtime. Effect: safe_read. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "plugin.runtime.status", @@ -11185,7 +11197,7 @@ }, { "additionalProperties": false, - "description": "Switch an installed plugin to one explicitly selected online source revision. Method: POST. Path: /api/v1/plugin/source/{plugin_id}. Effect: external_side_effect.", + "description": "Switch an installed plugin to one explicitly selected online source revision. Method: POST. Path: /api/v1/plugin/source/{plugin_id}. Effect: external_side_effect. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/PluginSourceChangeRequest", @@ -11222,7 +11234,7 @@ }, { "additionalProperties": false, - "description": "Install an unbound plugin from one explicitly selected online source. Method: POST. Path: /api/v1/plugin/source/{plugin_id}/install. Effect: external_side_effect.", + "description": "Install an unbound plugin from one explicitly selected online source. Method: POST. Path: /api/v1/plugin/source/{plugin_id}/install. Effect: external_side_effect. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/PluginSourceInstallRequest", @@ -11259,7 +11271,7 @@ }, { "additionalProperties": false, - "description": "Inspect source candidates and the current immutable source identity before installation or source change. Method: GET. Path: /api/v1/plugin/source/{plugin_id}. Effect: safe_read.", + "description": "Inspect source candidates and the current immutable source identity before installation or source change. Method: GET. Path: /api/v1/plugin/source/{plugin_id}. Effect: safe_read. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "plugin.source.options", @@ -11291,7 +11303,7 @@ }, { "additionalProperties": false, - "description": "Read public installation statistics for plugins. Method: GET. Path: /api/v1/plugin/statistic. Effect: safe_read.", + "description": "Read public installation statistics for plugins. Method: GET. Path: /api/v1/plugin/statistic. Effect: safe_read. Identity scope: public. Required role: user.", "properties": { "operation_id": { "const": "plugin.statistics", @@ -11307,7 +11319,7 @@ }, { "additionalProperties": false, - "description": "Uninstall one plugin and remove it from the installed set. Method: DELETE. Path: /api/v1/plugin/{plugin_id}. Effect: destructive_write.", + "description": "Uninstall one plugin and remove it from the installed set. Method: DELETE. Path: /api/v1/plugin/{plugin_id}. Effect: destructive_write. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "plugin.uninstall", @@ -11339,7 +11351,7 @@ }, { "additionalProperties": false, - "description": "Read personalized media or music recommendations. Method: GET. Path: /api/v1/recommend/agent. Effect: safe_read. Collection response: data remains a list and successful gateway output adds collection.result_count. collection.total_count is omitted when the endpoint or its upstream source does not expose a total.", + "description": "Read public media or music charts and recommendations. Method: GET. Path: /api/v1/recommend/agent. Effect: safe_read. Identity scope: public. Required role: user. Collection response: data remains a list and successful gateway output adds collection.result_count. collection.total_count is omitted when the endpoint or its upstream source does not expose a total.", "properties": { "operation_id": { "const": "recommendation.list", @@ -11348,7 +11360,7 @@ }, "query": { "additionalProperties": false, - "description": "Filters and control values for recommendation.list. Read personalized media or music recommendations. Use only the named fields below.", + "description": "Filters and control values for recommendation.list. Read public media or music charts and recommendations. Use only the named fields below.", "properties": { "days": { "default": 14, @@ -11446,7 +11458,7 @@ }, { "additionalProperties": false, - "description": "List registered scheduler jobs and their current state. Method: GET. Path: /api/v1/dashboard/schedule. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", + "description": "List registered scheduler jobs and their current state. Method: GET. Path: /api/v1/dashboard/schedule. Effect: safe_read. Identity scope: admin. Required role: system_admin. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "scheduler.list", @@ -11502,7 +11514,7 @@ }, { "additionalProperties": false, - "description": "Read current progress for one exact scheduler job. Method: GET. Path: /api/v1/dashboard/schedule/{job_id}/progress. Effect: safe_read.", + "description": "Read current progress for one exact scheduler job. Method: GET. Path: /api/v1/dashboard/schedule/{job_id}/progress. Effect: safe_read. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "scheduler.progress", @@ -11534,7 +11546,7 @@ }, { "additionalProperties": false, - "description": "Run one registered scheduler job immediately. Method: GET. Path: /api/v1/system/runscheduler. Effect: external_side_effect.", + "description": "Run one registered scheduler job immediately. Method: GET. Path: /api/v1/system/runscheduler. Effect: external_side_effect. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "scheduler.run", @@ -11566,7 +11578,7 @@ }, { "additionalProperties": false, - "description": "Use the configured recommendation model to rank or recommend torrent search results. Method: POST. Path: /api/v1/search/recommend. Effect: external_side_effect.", + "description": "Use the configured recommendation model to rank or recommend torrent search results. Method: POST. Path: /api/v1/search/recommend. Effect: external_side_effect. Identity scope: bound_user. Required role: user.", "properties": { "body": { "$ref": "#/$defs/Body_recommend_search_results_api_v1_search_recommend_post", @@ -11586,7 +11598,7 @@ }, { "additionalProperties": false, - "description": "Read the most recent torrent-search context and result set. Method: GET. Path: /api/v1/search/last/context. Effect: safe_read.", + "description": "Read the most recent torrent-search context and result set. Method: GET. Path: /api/v1/search/last/context. Effect: safe_read. Identity scope: bound_user. Required role: user.", "properties": { "operation_id": { "const": "search.results", @@ -11602,7 +11614,7 @@ }, { "additionalProperties": false, - "description": "Search torrent sites directly from a free-form title and optional media filters. Method: GET. Path: /api/v1/search/title. Effect: external_side_effect. Collection response: data remains a list and successful gateway output adds collection.result_count. collection.total_count is omitted when the endpoint or its upstream source does not expose a total.", + "description": "Search torrent sites directly from a free-form title and optional media filters. Method: GET. Path: /api/v1/search/title. Effect: external_side_effect. Identity scope: bound_user. Required role: user. Collection response: data remains a list and successful gateway output adds collection.result_count. collection.total_count is omitted when the endpoint or its upstream source does not expose a total.", "properties": { "operation_id": { "const": "search.title", @@ -11680,7 +11692,7 @@ }, { "additionalProperties": false, - "description": "Search torrent sites for one canonical media identity. Method: GET. Path: /api/v1/search/media/{media_id}. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", + "description": "Search torrent sites for one canonical media identity. Method: GET. Path: /api/v1/search/media/{media_id}. Effect: safe_read. Identity scope: bound_user. Required role: user. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "search.torrents", @@ -11827,7 +11839,7 @@ }, { "additionalProperties": false, - "description": "Create one configured site with its complete authentication and search settings. Method: POST. Path: /api/v1/site/. Effect: reversible_write.", + "description": "Create one configured site with its complete authentication and search settings. Method: POST. Path: /api/v1/site/. Effect: reversible_write. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/Site-Input", @@ -11848,7 +11860,7 @@ }, { "additionalProperties": false, - "description": "List site-account authentication providers and their required input definitions. Method: GET. Path: /api/v1/site/auth. Effect: safe_read.", + "description": "List site-account authentication providers and their required input definitions. Method: GET. Path: /api/v1/site/auth. Effect: safe_read. Identity scope: bound_user. Required role: user.", "properties": { "operation_id": { "const": "site.auth.options", @@ -11864,7 +11876,7 @@ }, { "additionalProperties": false, - "description": "Authenticate a supported site account and persist the resulting site authorization state. Method: POST. Path: /api/v1/site/auth. Effect: external_side_effect.", + "description": "Authenticate a supported site account and persist the resulting site authorization state. Method: POST. Path: /api/v1/site/auth. Effect: external_side_effect. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/SiteAuth", @@ -11885,7 +11897,7 @@ }, { "additionalProperties": false, - "description": "List torrent categories supported by one configured site. Method: GET. Path: /api/v1/site/category/{site_id}. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", + "description": "List torrent categories supported by one configured site. Method: GET. Path: /api/v1/site/category/{site_id}. Effect: safe_read. Identity scope: bound_user. Required role: user. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "site.category", @@ -11957,7 +11969,7 @@ }, { "additionalProperties": false, - "description": "Persist a browser-obtained authentication cookie and optional User-Agent for one site without replacing other settings. Method: POST. Path: /api/v1/site/cookie/{site_id}/set. Effect: reversible_write.", + "description": "Persist a browser-obtained authentication cookie and optional User-Agent for one site without replacing other settings. Method: POST. Path: /api/v1/site/cookie/{site_id}/set. Effect: reversible_write. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/SiteCookieSet", @@ -11994,7 +12006,7 @@ }, { "additionalProperties": false, - "description": "Log in to one site and refresh its stored authentication cookie. Method: POST. Path: /api/v1/site/cookie/{site_id}. Effect: reversible_write.", + "description": "Log in to one site and refresh its stored authentication cookie. Method: POST. Path: /api/v1/site/cookie/{site_id}. Effect: reversible_write. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/SiteCookieUpdate", @@ -12031,7 +12043,7 @@ }, { "additionalProperties": false, - "description": "Start a CookieCloud synchronization of configured sites. Method: POST. Path: /api/v1/site/cookiecloud. Effect: external_side_effect.", + "description": "Start a CookieCloud synchronization of configured sites. Method: POST. Path: /api/v1/site/cookiecloud. Effect: external_side_effect. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "site.cookiecloud.sync", @@ -12047,7 +12059,7 @@ }, { "additionalProperties": false, - "description": "Delete one configured site by persistent site ID. Method: DELETE. Path: /api/v1/site/{site_id}. Effect: destructive_write.", + "description": "Delete one configured site by persistent site ID. Method: DELETE. Path: /api/v1/site/{site_id}. Effect: destructive_write. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "site.delete", @@ -12079,7 +12091,7 @@ }, { "additionalProperties": false, - "description": "List configured sites with status/name filters; authentication fields are returned only to a superuser. Method: GET. Path: /api/v1/site/agent. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", + "description": "List configured sites with status/name filters; authentication fields are returned only to a superuser. Method: GET. Path: /api/v1/site/agent. Effect: safe_read. Identity scope: bound_user. Required role: user. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "site.list", @@ -12158,7 +12170,7 @@ }, { "additionalProperties": false, - "description": "Read the configured site-domain to site-name mapping. Method: GET. Path: /api/v1/site/mapping. Effect: safe_read.", + "description": "Read the configured site-domain to site-name mapping. Method: GET. Path: /api/v1/site/mapping. Effect: safe_read. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "site.mapping", @@ -12174,7 +12186,7 @@ }, { "additionalProperties": false, - "description": "Replace priorities for the supplied configured site IDs. Method: POST. Path: /api/v1/site/priorities. Effect: reversible_write.", + "description": "Replace priorities for the supplied configured site IDs. Method: POST. Path: /api/v1/site/priorities. Effect: reversible_write. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "description": "Request value for site.priorities.update. Replace priorities for the supplied configured site IDs. Use the exact type and fields below.", @@ -12199,7 +12211,7 @@ }, { "additionalProperties": false, - "description": "Delete all configured sites and start a fresh CookieCloud synchronization. Method: POST. Path: /api/v1/site/reset. Effect: destructive_write.", + "description": "Delete all configured sites and start a fresh CookieCloud synchronization. Method: POST. Path: /api/v1/site/reset. Effect: destructive_write. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "site.reset", @@ -12215,7 +12227,7 @@ }, { "additionalProperties": false, - "description": "Browse torrent resources from one configured site with category and keyword filters. Method: GET. Path: /api/v1/site/resource/{site_id}. Effect: external_side_effect. Collection response: data remains a list and successful gateway output adds collection.result_count. collection.total_count is omitted when the endpoint or its upstream source does not expose a total.", + "description": "Browse torrent resources from one configured site with category and keyword filters. Method: GET. Path: /api/v1/site/resource/{site_id}. Effect: external_side_effect. Identity scope: admin. Required role: system_admin. Collection response: data remains a list and successful gateway output adds collection.result_count. collection.total_count is omitted when the endpoint or its upstream source does not expose a total.", "properties": { "operation_id": { "const": "site.resource", @@ -12309,7 +12321,7 @@ }, { "additionalProperties": false, - "description": "List configured sites selected for RSS subscription processing. Method: GET. Path: /api/v1/site/rss. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", + "description": "List configured sites selected for RSS subscription processing. Method: GET. Path: /api/v1/site/rss. Effect: safe_read. Identity scope: admin. Required role: system_admin. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "site.rss", @@ -12365,7 +12377,7 @@ }, { "additionalProperties": false, - "description": "List active configured sites supporting one exact media type. Method: GET. Path: /api/v1/site/media/{media_type}. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", + "description": "List active configured sites supporting one exact media type. Method: GET. Path: /api/v1/site/media/{media_type}. Effect: safe_read. Identity scope: admin. Required role: system_admin. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "site.searchable", @@ -12437,7 +12449,7 @@ }, { "additionalProperties": false, - "description": "Read account and traffic statistics for one exact configured site domain. Method: GET. Path: /api/v1/site/statistic/{site_url}. Effect: safe_read.", + "description": "Read account and traffic statistics for one exact configured site domain. Method: GET. Path: /api/v1/site/statistic/{site_url}. Effect: safe_read. Identity scope: bound_user. Required role: user.", "properties": { "operation_id": { "const": "site.statistic", @@ -12469,7 +12481,7 @@ }, { "additionalProperties": false, - "description": "Read the latest account and traffic statistics for all configured sites. Method: GET. Path: /api/v1/site/statistic. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", + "description": "Read the latest account and traffic statistics for all configured sites. Method: GET. Path: /api/v1/site/statistic. Effect: safe_read. Identity scope: bound_user. Required role: user. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "site.statistics", @@ -12525,7 +12537,7 @@ }, { "additionalProperties": false, - "description": "List indexer definitions supported by the installed MoviePilot resources. Method: GET. Path: /api/v1/site/supporting. Effect: safe_read.", + "description": "List indexer definitions supported by the installed MoviePilot resources. Method: GET. Path: /api/v1/site/supporting. Effect: safe_read. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "site.supporting", @@ -12541,7 +12553,7 @@ }, { "additionalProperties": false, - "description": "Test connectivity and authentication for one configured site. Method: GET. Path: /api/v1/site/test/{site_id}. Effect: safe_read.", + "description": "Test connectivity and authentication for one configured site. Method: GET. Path: /api/v1/site/test/{site_id}. Effect: safe_read. Identity scope: bound_user. Required role: user.", "properties": { "operation_id": { "const": "site.test", @@ -12573,7 +12585,7 @@ }, { "additionalProperties": false, - "description": "Update one configured site's complete settings. Method: PUT. Path: /api/v1/site/. Effect: reversible_write.", + "description": "Update one configured site's complete settings. Method: PUT. Path: /api/v1/site/. Effect: reversible_write. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/Site-Input", @@ -12594,7 +12606,7 @@ }, { "additionalProperties": false, - "description": "Read the latest account statistics collected from one site. Method: GET. Path: /api/v1/site/userdata/{site_id}. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", + "description": "Read the latest account statistics collected from one site. Method: GET. Path: /api/v1/site/userdata/{site_id}. Effect: safe_read. Identity scope: admin. Required role: system_admin. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "site.userdata", @@ -12678,7 +12690,7 @@ }, { "additionalProperties": false, - "description": "Read the latest collected account statistics for every configured site. Method: GET. Path: /api/v1/site/userdata/latest. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", + "description": "Read the latest collected account statistics for every configured site. Method: GET. Path: /api/v1/site/userdata/latest. Effect: safe_read. Identity scope: admin. Required role: system_admin. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "site.userdata.latest", @@ -12734,7 +12746,7 @@ }, { "additionalProperties": false, - "description": "Refresh and return account statistics for one configured site. Method: POST. Path: /api/v1/site/userdata/{site_id}. Effect: external_side_effect.", + "description": "Refresh and return account statistics for one configured site. Method: POST. Path: /api/v1/site/userdata/{site_id}. Effect: external_side_effect. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "site.userdata.refresh", @@ -12766,7 +12778,7 @@ }, { "additionalProperties": false, - "description": "List slash commands that the Agent may dispatch. Method: GET. Path: /api/v1/message/agent/commands. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", + "description": "List slash commands that the Agent may dispatch. Method: GET. Path: /api/v1/message/agent/commands. Effect: safe_read. Identity scope: admin. Required role: system_admin. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "slash.list", @@ -12822,7 +12834,7 @@ }, { "additionalProperties": false, - "description": "Execute one complete slash command through MoviePilot messaging. Method: POST. Path: /api/v1/message/agent/commands/run. Effect: external_side_effect.", + "description": "Execute one complete slash command through MoviePilot messaging. Method: POST. Path: /api/v1/message/agent/commands/run. Effect: external_side_effect. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/AgentCommandRunRequest", @@ -12843,7 +12855,7 @@ }, { "additionalProperties": false, - "description": "Delete one exact file or directory from a configured storage provider. Method: POST. Path: /api/v1/storage/delete. Effect: destructive_write.", + "description": "Delete one exact file or directory from a configured storage provider. Method: POST. Path: /api/v1/storage/delete. Effect: destructive_write. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/FileItem-Input", @@ -12864,7 +12876,7 @@ }, { "additionalProperties": false, - "description": "List files or directories from one configured storage location. Method: POST. Path: /api/v1/storage/agent/list. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", + "description": "List files or directories from one configured storage location. Method: POST. Path: /api/v1/storage/agent/list. Effect: safe_read. Identity scope: bound_user. Required role: user. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "body": { "$ref": "#/$defs/FileItem-Input", @@ -12950,7 +12962,7 @@ }, { "additionalProperties": false, - "description": "Run one provider-defined management action against an exact configured storage target. Method: POST. Path: /api/v1/storage/manage. Effect: external_side_effect.", + "description": "Run one provider-defined management action against an exact configured storage target. Method: POST. Path: /api/v1/storage/manage. Effect: external_side_effect. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/ManageRequest", @@ -12971,7 +12983,7 @@ }, { "additionalProperties": false, - "description": "Create a named child directory below one exact storage directory item. Method: POST. Path: /api/v1/storage/mkdir. Effect: reversible_write.", + "description": "Create a named child directory below one exact storage directory item. Method: POST. Path: /api/v1/storage/mkdir. Effect: reversible_write. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/FileItem-Input", @@ -13008,7 +13020,7 @@ }, { "additionalProperties": false, - "description": "Rename one exact storage item, optionally applying media-aware recursive renaming. Method: POST. Path: /api/v1/storage/rename. Effect: reversible_write.", + "description": "Rename one exact storage item, optionally applying media-aware recursive renaming. Method: POST. Path: /api/v1/storage/rename. Effect: reversible_write. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/FileItem-Input", @@ -13058,7 +13070,7 @@ }, { "additionalProperties": false, - "description": "Read configured directory or storage settings. Method: GET. Path: /api/v1/storage/directories. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", + "description": "Read configured directory or storage settings. Method: GET. Path: /api/v1/storage/directories. Effect: safe_read. Identity scope: admin. Required role: system_admin. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "storage.settings", @@ -13138,7 +13150,7 @@ }, { "additionalProperties": false, - "description": "Create one movie, TV, or music subscription. Method: POST. Path: /api/v1/subscribe/. Effect: reversible_write.", + "description": "Create one movie, TV, or music subscription. Method: POST. Path: /api/v1/subscribe/. Effect: reversible_write. Identity scope: bound_user. Required role: user.", "properties": { "body": { "$ref": "#/$defs/Subscribe", @@ -13159,7 +13171,7 @@ }, { "additionalProperties": false, - "description": "Delete one active subscription. Method: DELETE. Path: /api/v1/subscribe/{subscribe_id}. Effect: destructive_write.", + "description": "Delete one active subscription. Method: DELETE. Path: /api/v1/subscribe/{subscribe_id}. Effect: destructive_write. Identity scope: bound_user. Required role: user.", "properties": { "operation_id": { "const": "subscription.delete", @@ -13191,7 +13203,7 @@ }, { "additionalProperties": false, - "description": "Delete accessible subscriptions matching one canonical media identity. Method: DELETE. Path: /api/v1/subscribe/media/{media_id}. Effect: destructive_write.", + "description": "Delete accessible subscriptions matching one canonical media identity. Method: DELETE. Path: /api/v1/subscribe/media/{media_id}. Effect: destructive_write. Identity scope: bound_user. Required role: user.", "properties": { "operation_id": { "const": "subscription.delete_by_media", @@ -13262,7 +13274,7 @@ }, { "additionalProperties": false, - "description": "Request cancellation of one durable subscription-search batch before its download side-effect boundary. Method: PUT. Path: /api/v1/subscribe/execution/batches/{batch_id}/cancel. Effect: external_side_effect.", + "description": "Request cancellation of one durable subscription-search batch before its download side-effect boundary. Method: PUT. Path: /api/v1/subscribe/execution/batches/{batch_id}/cancel. Effect: external_side_effect. Identity scope: bound_user. Required role: user.", "properties": { "operation_id": { "const": "subscription.execution.cancel", @@ -13294,7 +13306,7 @@ }, { "additionalProperties": false, - "description": "Read one durable subscription-search batch visible to the current user. Method: GET. Path: /api/v1/subscribe/execution/batches/{batch_id}. Effect: safe_read.", + "description": "Read one durable subscription-search batch visible to the current user. Method: GET. Path: /api/v1/subscribe/execution/batches/{batch_id}. Effect: safe_read. Identity scope: bound_user. Required role: user.", "properties": { "operation_id": { "const": "subscription.execution.get", @@ -13326,7 +13338,7 @@ }, { "additionalProperties": false, - "description": "List recent durable subscription-search batches visible to the current user. Method: GET. Path: /api/v1/subscribe/execution/batches. Effect: safe_read. Collection response: data remains a list and successful gateway output adds collection.result_count. collection.total_count is omitted when the endpoint or its upstream source does not expose a total.", + "description": "List recent durable subscription-search batches visible to the current user. Method: GET. Path: /api/v1/subscribe/execution/batches. Effect: safe_read. Identity scope: bound_user. Required role: user. Collection response: data remains a list and successful gateway output adds collection.result_count. collection.total_count is omitted when the endpoint or its upstream source does not expose a total.", "properties": { "operation_id": { "const": "subscription.execution.list", @@ -13361,7 +13373,7 @@ }, { "additionalProperties": false, - "description": "Read local library and transfer-file coverage for one accessible subscription. Method: GET. Path: /api/v1/subscribe/files/{subscribe_id}. Effect: safe_read.", + "description": "Read local library and transfer-file coverage for one accessible subscription. Method: GET. Path: /api/v1/subscribe/files/{subscribe_id}. Effect: safe_read. Identity scope: bound_user. Required role: user.", "properties": { "operation_id": { "const": "subscription.files", @@ -13393,7 +13405,7 @@ }, { "additionalProperties": false, - "description": "Find one accessible subscription by canonical media identity and optional season. Method: GET. Path: /api/v1/subscribe/media/{media_id}. Effect: safe_read.", + "description": "Find one accessible subscription by canonical media identity and optional season. Method: GET. Path: /api/v1/subscribe/media/{media_id}. Effect: safe_read. Identity scope: bound_user. Required role: user.", "properties": { "operation_id": { "const": "subscription.find", @@ -13500,7 +13512,7 @@ }, { "additionalProperties": false, - "description": "Follow one subscription-sharing user by exact share user ID. Method: POST. Path: /api/v1/subscribe/follow. Effect: reversible_write.", + "description": "Follow one subscription-sharing user by exact share user ID. Method: POST. Path: /api/v1/subscribe/follow. Effect: reversible_write. Identity scope: bound_user. Required role: user.", "properties": { "operation_id": { "const": "subscription.follow.add", @@ -13535,7 +13547,7 @@ }, { "additionalProperties": false, - "description": "Stop following one subscription-sharing user by exact share user ID. Method: DELETE. Path: /api/v1/subscribe/follow. Effect: reversible_write.", + "description": "Stop following one subscription-sharing user by exact share user ID. Method: DELETE. Path: /api/v1/subscribe/follow. Effect: reversible_write. Identity scope: bound_user. Required role: user.", "properties": { "operation_id": { "const": "subscription.follow.delete", @@ -13570,7 +13582,7 @@ }, { "additionalProperties": false, - "description": "List subscription-sharing user IDs followed by the current user. Method: GET. Path: /api/v1/subscribe/follow. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", + "description": "List subscription-sharing user IDs followed by the current user. Method: GET. Path: /api/v1/subscribe/follow. Effect: safe_read. Identity scope: bound_user. Required role: user. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "subscription.follow.list", @@ -13626,7 +13638,7 @@ }, { "additionalProperties": false, - "description": "Create a local subscription from one shared subscription definition. Method: POST. Path: /api/v1/subscribe/fork. Effect: external_side_effect.", + "description": "Create a local subscription from one shared subscription definition. Method: POST. Path: /api/v1/subscribe/fork. Effect: external_side_effect. Identity scope: bound_user. Required role: user.", "properties": { "body": { "$ref": "#/$defs/SubscribeShare", @@ -13647,7 +13659,7 @@ }, { "additionalProperties": false, - "description": "Read one accessible subscription by persistent subscription ID. Method: GET. Path: /api/v1/subscribe/{subscribe_id}. Effect: safe_read.", + "description": "Read one accessible subscription by persistent subscription ID. Method: GET. Path: /api/v1/subscribe/{subscribe_id}. Effect: safe_read. Identity scope: bound_user. Required role: user.", "properties": { "operation_id": { "const": "subscription.get", @@ -13679,7 +13691,7 @@ }, { "additionalProperties": false, - "description": "List completed or archived subscription records. Method: GET. Path: /api/v1/subscribe/history/{mtype}. Effect: safe_read. Collection response: data remains a list and the endpoint's documented pagination or limit defaults remain in effect. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count-only request, use the smallest valid page and read collection.total_count; do not query the database merely because item data is truncated.", + "description": "List completed or archived subscription records. Method: GET. Path: /api/v1/subscribe/history/{mtype}. Effect: safe_read. Identity scope: bound_user. Required role: user. Collection response: data remains a list and the endpoint's documented pagination or limit defaults remain in effect. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count-only request, use the smallest valid page and read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "subscription.history", @@ -13750,7 +13762,7 @@ }, { "additionalProperties": false, - "description": "Delete one accessible subscription-history record. Method: DELETE. Path: /api/v1/subscribe/history/{history_id}. Effect: destructive_write.", + "description": "Delete one accessible subscription-history record. Method: DELETE. Path: /api/v1/subscribe/history/{history_id}. Effect: destructive_write. Identity scope: bound_user. Required role: user.", "properties": { "operation_id": { "const": "subscription.history.delete", @@ -13782,7 +13794,7 @@ }, { "additionalProperties": false, - "description": "List active subscriptions. Method: GET. Path: /api/v1/subscribe/. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", + "description": "List active subscriptions. Method: GET. Path: /api/v1/subscribe/. Effect: safe_read. Identity scope: bound_user. Required role: user. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "subscription.list", @@ -13838,7 +13850,7 @@ }, { "additionalProperties": false, - "description": "Start a system-wide refresh of subscription TMDB metadata. Method: POST. Path: /api/v1/subscribe/check. Effect: external_side_effect.", + "description": "Start a system-wide refresh of subscription TMDB metadata. Method: POST. Path: /api/v1/subscribe/check. Effect: external_side_effect. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "subscription.metadata.refresh", @@ -13873,7 +13885,7 @@ }, { "additionalProperties": false, - "description": "List globally popular subscriptions with filters and pagination. Method: GET. Path: /api/v1/subscribe/popular. Effect: safe_read. Collection response: data remains a list and successful gateway output adds collection.result_count. collection.total_count is omitted when the endpoint or its upstream source does not expose a total.", + "description": "List globally popular subscriptions with filters and pagination. Method: GET. Path: /api/v1/subscribe/popular. Effect: safe_read. Identity scope: public. Required role: user. Collection response: data remains a list and successful gateway output adds collection.result_count. collection.total_count is omitted when the endpoint or its upstream source does not expose a total.", "properties": { "operation_id": { "const": "subscription.popular", @@ -13997,7 +14009,7 @@ }, { "additionalProperties": false, - "description": "Start the configured system-wide subscription refresh job. Method: POST. Path: /api/v1/subscribe/refresh. Effect: external_side_effect.", + "description": "Start the configured system-wide subscription refresh job. Method: POST. Path: /api/v1/subscribe/refresh. Effect: external_side_effect. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "subscription.refresh", @@ -14032,7 +14044,7 @@ }, { "additionalProperties": false, - "description": "Reset one accessible subscription so it can be processed again. Method: POST. Path: /api/v1/subscribe/reset/{subid}. Effect: reversible_write.", + "description": "Reset one accessible subscription so it can be processed again. Method: POST. Path: /api/v1/subscribe/reset/{subid}. Effect: reversible_write. Identity scope: bound_user. Required role: user.", "properties": { "operation_id": { "const": "subscription.reset", @@ -14064,7 +14076,7 @@ }, { "additionalProperties": false, - "description": "Run an immediate search for one existing subscription. Method: POST. Path: /api/v1/subscribe/search/{subscribe_id}. Effect: external_side_effect.", + "description": "Run an immediate search for one existing subscription. Method: POST. Path: /api/v1/subscribe/search/{subscribe_id}. Effect: external_side_effect. Identity scope: bound_user. Required role: user.", "properties": { "operation_id": { "const": "subscription.search", @@ -14115,7 +14127,7 @@ }, { "additionalProperties": false, - "description": "Start immediate searches for all subscriptions accessible to the current user. Method: POST. Path: /api/v1/subscribe/search. Effect: external_side_effect.", + "description": "Start immediate searches for all subscriptions accessible to the current user. Method: POST. Path: /api/v1/subscribe/search. Effect: external_side_effect. Identity scope: bound_user. Required role: user.", "properties": { "operation_id": { "const": "subscription.search_all", @@ -14150,7 +14162,7 @@ }, { "additionalProperties": false, - "description": "Publish one accessible subscription to the MoviePilot sharing service. Method: POST. Path: /api/v1/subscribe/share. Effect: external_side_effect.", + "description": "Publish one accessible subscription to the MoviePilot sharing service. Method: POST. Path: /api/v1/subscribe/share. Effect: external_side_effect. Identity scope: bound_user. Required role: user.", "properties": { "body": { "$ref": "#/$defs/SubscribeShare", @@ -14171,7 +14183,7 @@ }, { "additionalProperties": false, - "description": "Delete one shared-subscription publication by share ID. Method: DELETE. Path: /api/v1/subscribe/share/{share_id}. Effect: external_side_effect.", + "description": "Delete one shared-subscription publication by share ID. Method: DELETE. Path: /api/v1/subscribe/share/{share_id}. Effect: external_side_effect. Identity scope: bound_user. Required role: user.", "properties": { "operation_id": { "const": "subscription.share.delete", @@ -14203,7 +14215,7 @@ }, { "additionalProperties": false, - "description": "Read aggregate contribution and reuse counts for subscription sharers. Method: GET. Path: /api/v1/subscribe/share/statistics. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", + "description": "Read aggregate contribution and reuse counts for subscription sharers. Method: GET. Path: /api/v1/subscribe/share/statistics. Effect: safe_read. Identity scope: public. Required role: user. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "subscription.share.statistics", @@ -14259,7 +14271,7 @@ }, { "additionalProperties": false, - "description": "List shared subscriptions with filters and pagination. Method: GET. Path: /api/v1/subscribe/shares. Effect: safe_read. Collection response: data remains a list and successful gateway output adds collection.result_count. collection.total_count is omitted when the endpoint or its upstream source does not expose a total.", + "description": "List shared subscriptions with filters and pagination. Method: GET. Path: /api/v1/subscribe/shares. Effect: safe_read. Identity scope: public. Required role: user. Collection response: data remains a list and successful gateway output adds collection.result_count. collection.total_count is omitted when the endpoint or its upstream source does not expose a total.", "properties": { "operation_id": { "const": "subscription.shares", @@ -14374,7 +14386,7 @@ }, { "additionalProperties": false, - "description": "Set one accessible subscription to running, paused, or stopped state. Method: PUT. Path: /api/v1/subscribe/status/{subid}. Effect: reversible_write.", + "description": "Set one accessible subscription to running, paused, or stopped state. Method: PUT. Path: /api/v1/subscribe/status/{subid}. Effect: reversible_write. Identity scope: bound_user. Required role: user.", "properties": { "operation_id": { "const": "subscription.status.update", @@ -14422,7 +14434,7 @@ }, { "additionalProperties": false, - "description": "Update one existing movie, TV, or music subscription. Method: PUT. Path: /api/v1/subscribe/. Effect: reversible_write.", + "description": "Update one existing movie, TV, or music subscription. Method: PUT. Path: /api/v1/subscribe/. Effect: reversible_write. Identity scope: bound_user. Required role: user.", "properties": { "body": { "$ref": "#/$defs/Subscribe", @@ -14443,7 +14455,7 @@ }, { "additionalProperties": false, - "description": "List public subscriptions owned by one accessible MoviePilot username. Method: GET. Path: /api/v1/subscribe/user/{username}. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", + "description": "List public subscriptions owned by one accessible MoviePilot username. Method: GET. Path: /api/v1/subscribe/user/{username}. Effect: safe_read. Identity scope: bound_user. Required role: user. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "subscription.user.list", @@ -14515,7 +14527,7 @@ }, { "additionalProperties": false, - "description": "Search subtitle providers for one canonical media identity and optional season or episode. Method: GET. Path: /api/v1/search/subtitle/media/{media_id}. Effect: external_side_effect. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", + "description": "Search subtitle providers for one canonical media identity and optional season or episode. Method: GET. Path: /api/v1/search/subtitle/media/{media_id}. Effect: external_side_effect. Identity scope: bound_user. Required role: user. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "subtitle.search.media", @@ -14643,7 +14655,7 @@ }, { "additionalProperties": false, - "description": "Search subtitle providers from a free-form title and optional media filters. Method: GET. Path: /api/v1/search/subtitle/title. Effect: external_side_effect. Collection response: data remains a list and successful gateway output adds collection.result_count. collection.total_count is omitted when the endpoint or its upstream source does not expose a total.", + "description": "Search subtitle providers from a free-form title and optional media filters. Method: GET. Path: /api/v1/search/subtitle/title. Effect: external_side_effect. Identity scope: bound_user. Required role: user. Collection response: data remains a list and successful gateway output adds collection.result_count. collection.total_count is omitted when the endpoint or its upstream source does not expose a total.", "properties": { "operation_id": { "const": "subtitle.search.title", @@ -14709,7 +14721,7 @@ }, { "additionalProperties": false, - "description": "List MoviePilot host modules and service-type options for UI selectors. Method: GET. Path: /api/v1/system/module-catalog. Effect: safe_read.", + "description": "List MoviePilot host modules and service-type options for UI selectors. Method: GET. Path: /api/v1/system/module-catalog. Effect: safe_read. Identity scope: bound_user. Required role: user.", "properties": { "operation_id": { "const": "system.module.catalog", @@ -14725,7 +14737,7 @@ }, { "additionalProperties": false, - "description": "List enabled MoviePilot module IDs and localized names. Method: GET. Path: /api/v1/system/modulelist. Effect: safe_read.", + "description": "List enabled MoviePilot module IDs and localized names. Method: GET. Path: /api/v1/system/modulelist. Effect: safe_read. Identity scope: bound_user. Required role: user.", "properties": { "operation_id": { "const": "system.module.list", @@ -14741,7 +14753,7 @@ }, { "additionalProperties": false, - "description": "List built-in MoviePilot modules that can be enabled or disabled by an administrator. Method: GET. Path: /api/v1/system/module-settings. Effect: safe_read.", + "description": "List built-in MoviePilot modules that can be enabled or disabled by an administrator. Method: GET. Path: /api/v1/system/module-settings. Effect: safe_read. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "system.module.settings", @@ -14757,7 +14769,7 @@ }, { "additionalProperties": false, - "description": "Run the built-in availability test for one enabled MoviePilot module. Method: GET. Path: /api/v1/system/moduletest/{moduleid}. Effect: external_side_effect.", + "description": "Run the built-in availability test for one enabled MoviePilot module. Method: GET. Path: /api/v1/system/moduletest/{moduleid}. Effect: external_side_effect. Identity scope: bound_user. Required role: user.", "properties": { "operation_id": { "const": "system.module.test", @@ -14789,7 +14801,7 @@ }, { "additionalProperties": false, - "description": "List approved network-test targets with sanitized destination addresses, without exposing request paths, query parameters, or credentials. Method: GET. Path: /api/v1/system/nettest/targets. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", + "description": "List approved network-test targets with sanitized destination addresses, without exposing request paths, query parameters, or credentials. Method: GET. Path: /api/v1/system/nettest/targets. Effect: safe_read. Identity scope: bound_user. Required role: user. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "system.network.targets", @@ -14845,7 +14857,7 @@ }, { "additionalProperties": false, - "description": "Test connectivity to one approved target or the legacy constrained URL input. Method: GET. Path: /api/v1/system/nettest. Effect: external_side_effect.", + "description": "Test connectivity to one approved target or the legacy constrained URL input. Method: GET. Path: /api/v1/system/nettest. Effect: external_side_effect. Identity scope: bound_user. Required role: user.", "properties": { "operation_id": { "const": "system.network.test", @@ -14904,7 +14916,7 @@ }, { "additionalProperties": false, - "description": "Restart the running MoviePilot process. Method: GET. Path: /api/v1/system/restart. Effect: external_side_effect.", + "description": "Restart the running MoviePilot process. Method: GET. Path: /api/v1/system/restart. Effect: external_side_effect. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "system.restart", @@ -14920,7 +14932,7 @@ }, { "additionalProperties": false, - "description": "Check for the latest stable MoviePilot v3 application release and current-platform site resources. Method: POST. Path: /api/v1/system/update/check. Effect: external_side_effect.", + "description": "Check for the latest stable MoviePilot v3 application release and current-platform site resources. Method: POST. Path: /api/v1/system/update/check. Effect: external_side_effect. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "system.update.check", @@ -14936,7 +14948,7 @@ }, { "additionalProperties": false, - "description": "Start downloading and verifying one selected application or site-resource update in the background. Method: POST. Path: /api/v1/system/update/download. Effect: external_side_effect.", + "description": "Start downloading and verifying one selected application or site-resource update in the background. Method: POST. Path: /api/v1/system/update/download. Effect: external_side_effect. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "anyOf": [ @@ -14964,7 +14976,7 @@ }, { "additionalProperties": false, - "description": "Install one selected already downloaded and verified application or site-resource update, then restart MoviePilot. Method: POST. Path: /api/v1/system/update/install. Effect: external_side_effect.", + "description": "Install one selected already downloaded and verified application or site-resource update, then restart MoviePilot. Method: POST. Path: /api/v1/system/update/install. Effect: external_side_effect. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "anyOf": [ @@ -14992,7 +15004,7 @@ }, { "additionalProperties": false, - "description": "Read application and site-resource update checks, downloads, verification, or install state. Method: GET. Path: /api/v1/system/update/status. Effect: safe_read.", + "description": "Read application and site-resource update checks, downloads, verification, or install state. Method: GET. Path: /api/v1/system/update/status. Effect: safe_read. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "system.update.status", @@ -15008,7 +15020,7 @@ }, { "additionalProperties": false, - "description": "Update to the current v3 development branch and restart MoviePilot. Method: POST. Path: /api/v1/system/upgrade. Effect: external_side_effect.", + "description": "Update to the current v3 development branch and restart MoviePilot. Method: POST. Path: /api/v1/system/upgrade. Effect: external_side_effect. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "const": "dev", @@ -15030,7 +15042,7 @@ }, { "additionalProperties": false, - "description": "Read the installation version and runtime usage report available to the current user. Method: GET. Path: /api/v1/system/usage/statistic. Effect: safe_read.", + "description": "Read the installation version and runtime usage report available to the current user. Method: GET. Path: /api/v1/system/usage/statistic. Effect: safe_read. Identity scope: bound_user. Required role: user.", "properties": { "operation_id": { "const": "system.usage.statistics", @@ -15046,7 +15058,7 @@ }, { "additionalProperties": false, - "description": "List available MoviePilot GitHub releases. Method: GET. Path: /api/v1/system/versions. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", + "description": "List available MoviePilot GitHub releases. Method: GET. Path: /api/v1/system/versions. Effect: safe_read. Identity scope: admin. Required role: system_admin. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "system.versions", @@ -15102,7 +15114,7 @@ }, { "additionalProperties": false, - "description": "Delete every cached torrent context. Method: DELETE. Path: /api/v1/torrent/cache. Effect: destructive_write.", + "description": "Delete every cached torrent context. Method: DELETE. Path: /api/v1/torrent/cache. Effect: destructive_write. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "torrent.cache.clear", @@ -15118,7 +15130,7 @@ }, { "additionalProperties": false, - "description": "Delete one cached torrent context by site domain and cache hash. Method: DELETE. Path: /api/v1/torrent/cache/{domain}/{torrent_hash}. Effect: destructive_write.", + "description": "Delete one cached torrent context by site domain and cache hash. Method: DELETE. Path: /api/v1/torrent/cache/{domain}/{torrent_hash}. Effect: destructive_write. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "torrent.cache.delete", @@ -15156,7 +15168,7 @@ }, { "additionalProperties": false, - "description": "Inspect cached torrent contexts and their recognized media identities. Method: GET. Path: /api/v1/torrent/cache. Effect: safe_read.", + "description": "Inspect cached torrent contexts and their recognized media identities. Method: GET. Path: /api/v1/torrent/cache. Effect: safe_read. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "torrent.cache.get", @@ -15172,7 +15184,7 @@ }, { "additionalProperties": false, - "description": "Refresh torrent caches from configured RSS or spider sources. Method: POST. Path: /api/v1/torrent/cache/refresh. Effect: external_side_effect.", + "description": "Refresh torrent caches from configured RSS or spider sources. Method: POST. Path: /api/v1/torrent/cache/refresh. Effect: external_side_effect. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "torrent.cache.refresh", @@ -15188,7 +15200,7 @@ }, { "additionalProperties": false, - "description": "Replace or recompute the media identity for one cached torrent context. Method: POST. Path: /api/v1/torrent/cache/reidentify/{domain}/{torrent_hash}. Effect: reversible_write.", + "description": "Replace or recompute the media identity for one cached torrent context. Method: POST. Path: /api/v1/torrent/cache/reidentify/{domain}/{torrent_hash}. Effect: reversible_write. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "torrent.cache.reidentify", @@ -15273,7 +15285,7 @@ }, { "additionalProperties": false, - "description": "Recommend an episode-number extraction template from supplied file samples. Method: POST. Path: /api/v1/transfer/episode-format/recommend. Effect: safe_read.", + "description": "Recommend an episode-number extraction template from supplied file samples. Method: POST. Path: /api/v1/transfer/episode-format/recommend. Effect: safe_read. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/EpisodeFormatRecommendItem", @@ -15294,7 +15306,7 @@ }, { "additionalProperties": false, - "description": "Run MoviePilot's manual file-transfer and organization workflow. Method: POST. Path: /api/v1/transfer/manual. Effect: external_side_effect.", + "description": "Run MoviePilot's manual file-transfer and organization workflow. Method: POST. Path: /api/v1/transfer/manual. Effect: external_side_effect. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/ManualTransferItem", @@ -15335,7 +15347,7 @@ }, { "additionalProperties": false, - "description": "List file-transfer history with filters and pagination. Method: GET. Path: /api/v1/history/transfer. Effect: safe_read. Collection response: items stay in data.list and the exact total stays in data.total. For a count-only request, use the smallest documented page and read that total; do not query the database merely because item data is truncated.", + "description": "List file-transfer history with filters and pagination. Method: GET. Path: /api/v1/history/transfer. Effect: safe_read. Identity scope: bound_user. Required role: user. Collection response: items stay in data.list and the exact total stays in data.total. For a count-only request, use the smallest documented page and read that total; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "transfer.history", @@ -15414,7 +15426,7 @@ }, { "additionalProperties": false, - "description": "Delete legacy transfer-history records while leaving files and durable failed-task records untouched. Method: DELETE. Path: /api/v1/history/transfer/all. Effect: destructive_write.", + "description": "Delete legacy transfer-history records while leaving files and durable failed-task records untouched. Method: DELETE. Path: /api/v1/history/transfer/all. Effect: destructive_write. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "transfer.history.clear", @@ -15430,7 +15442,7 @@ }, { "additionalProperties": false, - "description": "Delete one transfer-history record and optionally remove files. Method: DELETE. Path: /api/v1/history/transfer. Effect: destructive_write.", + "description": "Delete one transfer-history record and optionally remove files. Method: DELETE. Path: /api/v1/history/transfer. Effect: destructive_write. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/TransferHistory-Input", @@ -15484,7 +15496,7 @@ }, { "additionalProperties": false, - "description": "Start AI-assisted reorganization for one transfer-history record. Method: POST. Path: /api/v1/history/transfer/{history_id}/ai-redo. Effect: external_side_effect.", + "description": "Start AI-assisted reorganization for one transfer-history record. Method: POST. Path: /api/v1/history/transfer/{history_id}/ai-redo. Effect: external_side_effect. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "transfer.history.redo", @@ -15516,7 +15528,7 @@ }, { "additionalProperties": false, - "description": "Start AI-assisted reorganization for an explicit list of transfer-history records. Method: POST. Path: /api/v1/history/transfer/ai-redo. Effect: external_side_effect.", + "description": "Start AI-assisted reorganization for an explicit list of transfer-history records. Method: POST. Path: /api/v1/history/transfer/ai-redo. Effect: external_side_effect. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/BatchTransferHistoryRedoRequest", @@ -15537,7 +15549,7 @@ }, { "additionalProperties": false, - "description": "Check whether supplied storage items already have successful transfer history. Method: POST. Path: /api/v1/transfer/manual/history. Effect: safe_read.", + "description": "Check whether supplied storage items already have successful transfer history. Method: POST. Path: /api/v1/transfer/manual/history. Effect: safe_read. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/ManualTransferItem", @@ -15558,7 +15570,7 @@ }, { "additionalProperties": false, - "description": "Read one durable transfer task awaiting manual review. Method: GET. Path: /api/v1/transfer/tasks/{task_id}/manual-review. Effect: safe_read.", + "description": "Read one durable transfer task awaiting manual review. Method: GET. Path: /api/v1/transfer/tasks/{task_id}/manual-review. Effect: safe_read. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "transfer.manual_review", @@ -15590,7 +15602,7 @@ }, { "additionalProperties": false, - "description": "Record the authorized decision for one durable transfer manual-review operation. Method: POST. Path: /api/v1/transfer/tasks/{task_id}/manual-review. Effect: reversible_write.", + "description": "Record the authorized decision for one durable transfer manual-review operation. Method: POST. Path: /api/v1/transfer/tasks/{task_id}/manual-review. Effect: reversible_write. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/TransferManualReviewRequest", @@ -15627,7 +15639,7 @@ }, { "additionalProperties": false, - "description": "Page durable transfer tasks awaiting manual review or retry recovery. Method: GET. Path: /api/v1/transfer/tasks/manual-reviews. Effect: safe_read. Collection response: items stay in data.items and the exact total stays in data.total. For a count-only request, use the smallest documented page and read that total; do not query the database merely because item data is truncated.", + "description": "Page durable transfer tasks awaiting manual review or retry recovery. Method: GET. Path: /api/v1/transfer/tasks/manual-reviews. Effect: safe_read. Identity scope: admin. Required role: system_admin. Collection response: items stay in data.items and the exact total stays in data.total. For a count-only request, use the smallest documented page and read that total; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "transfer.manual_reviews", @@ -15681,7 +15693,7 @@ }, { "additionalProperties": false, - "description": "Preview the organized destination name for one source path and media identity. Method: GET. Path: /api/v1/transfer/name. Effect: safe_read.", + "description": "Preview the organized destination name for one source path and media identity. Method: GET. Path: /api/v1/transfer/name. Effect: safe_read. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "transfer.name", @@ -15719,7 +15731,7 @@ }, { "additionalProperties": false, - "description": "List items waiting in the file-transfer queue. Method: GET. Path: /api/v1/transfer/queue. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", + "description": "List items waiting in the file-transfer queue. Method: GET. Path: /api/v1/transfer/queue. Effect: safe_read. Identity scope: bound_user. Required role: user. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "transfer.queue", @@ -15775,7 +15787,7 @@ }, { "additionalProperties": false, - "description": "Remove one exact storage item from the file-transfer queue and stop its transfer. Method: DELETE. Path: /api/v1/transfer/queue. Effect: destructive_write.", + "description": "Remove one exact storage item from the file-transfer queue and stop its transfer. Method: DELETE. Path: /api/v1/transfer/queue. Effect: destructive_write. Identity scope: bound_user. Required role: user.", "properties": { "body": { "$ref": "#/$defs/FileItem-Input", @@ -15796,7 +15808,7 @@ }, { "additionalProperties": false, - "description": "Resolve the configured transfer destination for supplied source storage items. Method: POST. Path: /api/v1/transfer/manual/target-path. Effect: safe_read.", + "description": "Resolve the configured transfer destination for supplied source storage items. Method: POST. Path: /api/v1/transfer/manual/target-path. Effect: safe_read. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/ManualTransferItem", @@ -15817,7 +15829,7 @@ }, { "additionalProperties": false, - "description": "List built-in workflow action definitions and their parameter contracts. Method: GET. Path: /api/v1/workflow/actions. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", + "description": "List built-in workflow action definitions and their parameter contracts. Method: GET. Path: /api/v1/workflow/actions. Effect: safe_read. Identity scope: admin. Required role: system_admin. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "workflow.actions", @@ -15873,7 +15885,7 @@ }, { "additionalProperties": false, - "description": "Create one workflow from a complete workflow definition. Method: POST. Path: /api/v1/workflow/. Effect: reversible_write.", + "description": "Create one workflow from a complete workflow definition. Method: POST. Path: /api/v1/workflow/. Effect: reversible_write. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/Workflow-Input", @@ -15894,7 +15906,7 @@ }, { "additionalProperties": false, - "description": "Delete one configured workflow by persistent workflow ID. Method: DELETE. Path: /api/v1/workflow/{workflow_id}. Effect: destructive_write.", + "description": "Delete one configured workflow by persistent workflow ID. Method: DELETE. Path: /api/v1/workflow/{workflow_id}. Effect: destructive_write. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "workflow.delete", @@ -15926,7 +15938,7 @@ }, { "additionalProperties": false, - "description": "List event types that can trigger workflows. Method: GET. Path: /api/v1/workflow/event_types. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", + "description": "List event types that can trigger workflows. Method: GET. Path: /api/v1/workflow/event_types. Effect: safe_read. Identity scope: admin. Required role: system_admin. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "workflow.event_types", @@ -15982,7 +15994,7 @@ }, { "additionalProperties": false, - "description": "Create a local workflow from one shared workflow definition. Method: POST. Path: /api/v1/workflow/fork. Effect: external_side_effect.", + "description": "Create a local workflow from one shared workflow definition. Method: POST. Path: /api/v1/workflow/fork. Effect: external_side_effect. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/WorkflowShare", @@ -16003,7 +16015,7 @@ }, { "additionalProperties": false, - "description": "Read one complete configured workflow definition. Method: GET. Path: /api/v1/workflow/{workflow_id}. Effect: safe_read.", + "description": "Read one complete configured workflow definition. Method: GET. Path: /api/v1/workflow/{workflow_id}. Effect: safe_read. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "workflow.get", @@ -16035,7 +16047,7 @@ }, { "additionalProperties": false, - "description": "List configured workflows and their execution state. Method: GET. Path: /api/v1/workflow/agent. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", + "description": "List configured workflows and their execution state. Method: GET. Path: /api/v1/workflow/agent. Effect: safe_read. Identity scope: bound_user. Required role: user. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "workflow.list", @@ -16129,7 +16141,7 @@ }, { "additionalProperties": false, - "description": "Disable automatic execution of one configured workflow. Method: POST. Path: /api/v1/workflow/{workflow_id}/pause. Effect: reversible_write.", + "description": "Disable automatic execution of one configured workflow. Method: POST. Path: /api/v1/workflow/{workflow_id}/pause. Effect: reversible_write. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "workflow.pause", @@ -16161,7 +16173,7 @@ }, { "additionalProperties": false, - "description": "List workflow actions contributed by installed plugins, optionally filtered by plugin ID. Method: GET. Path: /api/v1/workflow/plugin/actions. Effect: safe_read. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", + "description": "List workflow actions contributed by installed plugins, optionally filtered by plugin ID. Method: GET. Path: /api/v1/workflow/plugin/actions. Effect: safe_read. Identity scope: admin. Required role: system_admin. Collection response: data remains a list; omit both page and count to preserve the legacy complete result. Successful gateway output adds collection.result_count and the exact collection.total_count. For a count or summary, send page=1 and count=1, then read collection.total_count; do not query the database merely because item data is truncated.", "properties": { "operation_id": { "const": "workflow.plugin.actions", @@ -16222,7 +16234,7 @@ }, { "additionalProperties": false, - "description": "Reset one configured workflow definition and execution state. Method: POST. Path: /api/v1/workflow/{workflow_id}/reset. Effect: reversible_write.", + "description": "Reset one configured workflow definition and execution state. Method: POST. Path: /api/v1/workflow/{workflow_id}/reset. Effect: reversible_write. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "workflow.reset", @@ -16254,7 +16266,7 @@ }, { "additionalProperties": false, - "description": "Run one configured workflow from the beginning or resume point. Method: POST. Path: /api/v1/workflow/{workflow_id}/run. Effect: external_side_effect.", + "description": "Run one configured workflow from the beginning or resume point. Method: POST. Path: /api/v1/workflow/{workflow_id}/run. Effect: external_side_effect. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "workflow.run", @@ -16306,7 +16318,7 @@ }, { "additionalProperties": false, - "description": "Publish one configured workflow to the MoviePilot sharing service. Method: POST. Path: /api/v1/workflow/share. Effect: external_side_effect.", + "description": "Publish one configured workflow to the MoviePilot sharing service. Method: POST. Path: /api/v1/workflow/share. Effect: external_side_effect. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/WorkflowShare", @@ -16327,7 +16339,7 @@ }, { "additionalProperties": false, - "description": "Delete one shared-workflow publication by share ID. Method: DELETE. Path: /api/v1/workflow/share/{share_id}. Effect: external_side_effect.", + "description": "Delete one shared-workflow publication by share ID. Method: DELETE. Path: /api/v1/workflow/share/{share_id}. Effect: external_side_effect. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "workflow.share.delete", @@ -16359,7 +16371,7 @@ }, { "additionalProperties": false, - "description": "List shared workflows with name and pagination filters. Method: GET. Path: /api/v1/workflow/shares. Effect: safe_read. Collection response: data remains a list and successful gateway output adds collection.result_count. collection.total_count is omitted when the endpoint or its upstream source does not expose a total.", + "description": "List shared workflows with name and pagination filters. Method: GET. Path: /api/v1/workflow/shares. Effect: safe_read. Identity scope: admin. Required role: system_admin. Collection response: data remains a list and successful gateway output adds collection.result_count. collection.total_count is omitted when the endpoint or its upstream source does not expose a total.", "properties": { "operation_id": { "const": "workflow.shares", @@ -16426,7 +16438,7 @@ }, { "additionalProperties": false, - "description": "Enable automatic execution of one configured workflow. Method: POST. Path: /api/v1/workflow/{workflow_id}/start. Effect: reversible_write.", + "description": "Enable automatic execution of one configured workflow. Method: POST. Path: /api/v1/workflow/{workflow_id}/start. Effect: reversible_write. Identity scope: admin. Required role: system_admin.", "properties": { "operation_id": { "const": "workflow.start", @@ -16458,7 +16470,7 @@ }, { "additionalProperties": false, - "description": "Replace one configured workflow definition. Method: PUT. Path: /api/v1/workflow/{workflow_id}. Effect: reversible_write.", + "description": "Replace one configured workflow definition. Method: PUT. Path: /api/v1/workflow/{workflow_id}. Effect: reversible_write. Identity scope: admin. Required role: system_admin.", "properties": { "body": { "$ref": "#/$defs/Workflow-Input", diff --git a/app/agent/tools/impl/api.py b/app/agent/tools/impl/api.py index f11f5d2a9..a187418d1 100644 --- a/app/agent/tools/impl/api.py +++ b/app/agent/tools/impl/api.py @@ -15,7 +15,7 @@ from app.agent.api.arguments import ( coerce_string_body, ) from app.agent.api.executor import ApiExecutionContext, ApiExecutionError, MoviePilotApiExecutor -from app.agent.policy.api import resolve_api_operation +from app.agent.policy.api import API_UNBOUND_READ_OPERATIONS, resolve_api_operation from app.agent.policy.contracts import ExecutionOutcome, PrincipalRole from app.agent.policy.sanitizer import summarize_input from app.agent.tools.base import MoviePilotTool @@ -228,7 +228,11 @@ class MoviePilotApiTool(MoviePilotTool): ) -> tuple[str, Optional[str], bool]: """把 Web、渠道或集成身份解析为真实 MoviePilot API 用户身份。""" raw_user_id = str(self._user_id or "") - if self._source == "api" and bool(self._agent_context.get("is_admin")): + is_authenticated_integration = self._source == "api" or ( + self._channel == NotificationChannel.Web.value + and self._source in {"openai", "openai.responses", "anthropic"} + ) + if is_authenticated_integration and bool(self._agent_context.get("is_admin")): return await self._resolve_superuser_integration_identity() direct_user_channels = { NotificationChannel.Web.value, @@ -248,54 +252,90 @@ class MoviePilotApiTool(MoviePilotTool): return raw_user_id, self._username, bool(self._agent_context.get("is_admin")) raise ApiExecutionError("Agent 数据上下文未装配,无法解析 API 用户身份") - username = self._username - user = await self._data.users.async_get_by_name(username) if username else None - if user is None and not is_direct_user_channel and self._channel and raw_user_id: - try: - channel = NotificationChannel(self._channel) - except ValueError: - channel = None - binding_keys = ( - { - NotificationChannel.Telegram: ("telegram_userid",), - NotificationChannel.Discord: ("discord_userid",), - NotificationChannel.Wechat: ("wechat_userid",), - NotificationChannel.Feishu: ("feishu_userid", "feishu_openid"), - NotificationChannel.WechatClawBot: ("wechatclawbot_userid",), - NotificationChannel.Slack: ("slack_userid",), - NotificationChannel.VoceChat: ("vocechat_userid",), - NotificationChannel.SynologyChat: ("synologychat_userid",), - NotificationChannel.QQ: ("qq_userid", "qq_openid"), - }.get(channel) - if channel is not None - else None - ) - if binding_keys: - # 渠道只提供一个标识,userid/openid 是候选字段;仓储统一拒绝跨用户歧义。 - username = await self.run_blocking( - "db", - self._data.users.find_name_by_bindings, - {key: raw_user_id for key in binding_keys}, - match_any=True, - ) - user = await self._data.users.async_get_by_name(username) if username else None - if user is None and is_direct_user_channel and raw_user_id.isdigit(): - return raw_user_id, self._username, bool(self._agent_context.get("is_admin")) - if user is None or not user.is_active: - raise ApiExecutionError("当前 Agent 身份未绑定有效的 MoviePilot 用户") + if is_direct_user_channel: + # Web 入口的持久化用户 ID 是事实源,旧会话用户名不能切换到同名新账号。 + user = await self._data.users.async_get_by_id(int(raw_user_id)) if raw_user_id.isdigit() else None + elif not self._channel and not self._source: + # 无渠道的后台任务使用宿主持久化的用户名,保留定时任务的执行身份。 + user = await self._data.users.async_get_by_name(self._username) if self._username else None + else: + user = await self._resolve_channel_api_user(raw_user_id) + if user is None: + raise ApiExecutionError(self._identity_unavailable_message()) + if not user.is_active: + raise ApiExecutionError("当前 MoviePilot 用户已停用,请联系管理员启用该用户后重试。重新绑定不能解除停用。") return str(user.id), user.name, bool(user.is_superuser) + def _identity_unavailable_message(self) -> str: + """按真实前端入口提示绑定步骤;查询仅能确定没有唯一启用绑定,不臆断具体原因。""" + labels = { + NotificationChannel.Telegram.value: "Telegram用户", + NotificationChannel.Discord.value: "Discord用户", + NotificationChannel.Wechat.value: "企业微信用户", + NotificationChannel.Feishu.value: "飞书用户", + NotificationChannel.WechatClawBot.value: "微信 ClawBot 用户", + NotificationChannel.Slack.value: "Slack用户", + NotificationChannel.VoceChat.value: "VoceChat用户", + NotificationChannel.SynologyChat.value: "SynologyChat用户", + NotificationChannel.QQ.value: "QQ 用户 ID 或 QQ 用户 OpenID(填写与当前发送者 ID 对应的一项)", + } + label = labels.get(self._channel or "") + if label is None: + if self._channel in {NotificationChannel.Web.value, NotificationChannel.WebAgent.value}: + return "当前登录身份未对应有效的 MoviePilot 用户,请重新登录;若账号已删除,请联系管理员。此入口无需绑定通知渠道。" + return "当前任务身份未对应有效的 MoviePilot 用户,请联系管理员检查任务所属账号及身份配置。" + sender_id = json.dumps(str(self._user_id or ""), ensure_ascii=False) + return ( + "当前渠道账号未绑定唯一且已启用的 MoviePilot 用户,可能尚未绑定、绑定用户已停用或存在重复绑定。" + f"请登录要使用的 MoviePilot 账号,打开右上角头像 → 个人信息 → 账号绑定,在「{label}」中填写" + f"当前渠道发送者 ID {sender_id},然后保存并重试。请填写完整 ID,不要填写昵称、群 ID 或机器人 ID。" + "若已经填写,请管理员确认该用户已启用,且同一渠道 ID 没有绑定到多个启用用户。" + ) + + async def _resolve_channel_api_user(self, raw_user_id: str) -> Any: + """只按渠道稳定 ID 查找绑定用户,昵称和展示用户名不构成认证依据。""" + data = self._data + if data is None: + return None + try: + channel = NotificationChannel(self._channel) + except (TypeError, ValueError): + return None + binding_keys = { + NotificationChannel.Telegram: ("telegram_userid",), + NotificationChannel.Discord: ("discord_userid",), + NotificationChannel.Wechat: ("wechat_userid",), + NotificationChannel.Feishu: ("feishu_userid", "feishu_openid"), + NotificationChannel.WechatClawBot: ("wechatclawbot_userid",), + NotificationChannel.Slack: ("slack_userid",), + NotificationChannel.VoceChat: ("vocechat_userid",), + NotificationChannel.SynologyChat: ("synologychat_userid",), + NotificationChannel.QQ: ("qq_userid", "qq_openid"), + }.get(channel) + if not binding_keys or not raw_user_id: + return None + # userid/openid 是候选字段,仓储必须拒绝匹配到多个用户的歧义绑定。 + username = await self.run_blocking( + "db", data.users.find_name_by_bindings, + {key: raw_user_id for key in binding_keys}, match_any=True, + ) + return await data.users.async_get_by_name(username) if username else None + async def _get_executor( self, *, require_system_admin: bool = False, + public_read_only: bool = False, ) -> tuple[MoviePilotApiExecutor, bool]: - """返回当前 operation 的执行器及其管理员身份事实。""" + """解析执行身份;公共资料使用只能执行白名单读取的宿主认证通道。""" if self._executor is not None: return self._executor, await self.is_admin_user() - user_id, username, is_admin = await self._resolve_api_identity( - require_system_admin=require_system_admin, - ) + if public_read_only: + user_id, username, is_admin = await self._resolve_superuser_integration_identity() + else: + user_id, username, is_admin = await self._resolve_api_identity( + require_system_admin=require_system_admin, + ) return ( MoviePilotApiExecutor( context=ApiExecutionContext( @@ -305,6 +345,7 @@ class MoviePilotApiTool(MoviePilotTool): session_id=self._session_id, channel=self._channel, source=self._source, + public_read_only=public_read_only, ) ), is_admin, @@ -352,6 +393,7 @@ class MoviePilotApiTool(MoviePilotTool): requires_system_admin = spec.required_role is PrincipalRole.SYSTEM_ADMIN executor, is_admin = await self._get_executor( require_system_admin=requires_system_admin, + public_read_only=operation_id in API_UNBOUND_READ_OPERATIONS, ) if requires_system_admin and not is_admin: return json.dumps( diff --git a/app/application/security/user.py b/app/application/security/user.py index 7dfebe2d3..5829e8e55 100644 --- a/app/application/security/user.py +++ b/app/application/security/user.py @@ -153,6 +153,9 @@ class ChainUserRepository(Protocol): async def async_get_by_name(self, name: str) -> Optional[UserSnapshot]: """异步按用户名读取公开用户快照。""" + async def async_get_by_id(self, user_id: int) -> Optional[UserSnapshot]: + """按已认证持久化 ID 读取公开用户快照,避免依赖旧会话展示名。""" + def create_auxiliary(self, command: AuxiliaryUserCreate) -> UserAuthSnapshot: """原子创建辅助认证用户并返回已提交快照。""" diff --git a/app/db/adapters/user.py b/app/db/adapters/user.py index 22771b45b..a68a15a78 100644 --- a/app/db/adapters/user.py +++ b/app/db/adapters/user.py @@ -260,6 +260,11 @@ class TransactionalUserRepository(ChainUserRepository): async with self._async_session() as session: return await SqlAlchemyUserRepository(session).async_get_by_name(name) + async def async_get_by_id(self, user_id: int) -> Optional[UserSnapshot]: + """在独占异步会话中按持久化 ID 读取用户公开快照。""" + async with self._async_session() as session: + return await SqlAlchemyUserRepository(session).async_get_by_id(user_id) + def create_auxiliary(self, command: AuxiliaryUserCreate) -> UserAuthSnapshot: """在独占事务中创建辅助认证用户,提交失败时完整回滚。""" with self._sync_session() as session: diff --git a/docs/mcp-api.md b/docs/mcp-api.md index 0ebb74410..6ddd13b72 100644 --- a/docs/mcp-api.md +++ b/docs/mcp-api.md @@ -986,6 +986,24 @@ API KEY 集成签发短期本机令牌,并按 operation 执行权限、确认 网关按后端监听配置 `HOST` / `PORT` 通过 HTTP 直连本机 API,并绕过环境代理。 `0.0.0.0` / `::` 通配监听分别使用 `127.0.0.1` / `::1` 回环地址,显式 IPv6 地址按 URL 规范加方括号。 `APP_DOMAIN` 用于 Passkey 等对外地址,不作为网关基址;反代域名在容器内不可达时无需清空该配置。 +每个 Agent operation 声明 `identity_scope`,完整 231 项清单见 +[Agent API 身份审查表](refactor/agent-api-surface-audit.md#gateway-identity-audit): + +- `public`:已审计的公共影视搜索、人物作品、影片详情、季/集信息、音乐元数据及关联浏览、榜单、社区订阅分享/热门/统计和全站插件安装统计,无需绑定渠道用户。 +- `bound_user`:个人数据、实例信息和普通用户写操作,必须解析真实有效用户;仍由具体端点执行归属及管理权限校验。渠道只接受稳定发送者 ID 的唯一有效绑定,昵称不作为认证依据;Web 使用当前持久化用户 ID,旧会话用户名不能切换到其他账号。 +- `admin`:保留已有管理员授权及经验证的渠道/API 管理员集成语义。插件评分含当前安装实例的评分,查询和提交均与 REST 的超级管理员要求对齐。 + +公共查询由宿主完成本机 API 认证,执行器只能调用声明为 `public` 的固定操作; +不会开放匿名 REST 访问,也不会将该身份复用于个人数据或写入。 +`safe_read` 不等于公共数据:片名识别的 Context 可包含本地应用的识别词,文件识别、 +媒体源启用情况、搜索结果、媒体库、存储、规则和普通用户可见配置继续要求用户身份。 + +需要绑定的渠道操作失败时,网关会返回当前发送者 ID 及对应前端字段:登录要使用的 +MoviePilot 账号,进入右上角头像 → 个人信息 → 账号绑定,填写对应渠道字段后保存。 +不要填写昵称、群 ID 或机器人 ID。仓储未找到唯一启用用户时,仅提示未绑定、停用或 +重复绑定的可能性,不臆断具体原因;管理员需检查用户状态及重复绑定。Web 账号失效则 +提示重新登录,不要求绑定通知渠道。Agent 应转述网关的实际操作提示。 + 通知渠道的 `subscription.add`、`subscription.update`、`subscription.delete` 使用渠道账号绑定的 有效 MoviePilot 用户身份,渠道管理员也不会借用超级管理员身份。创建时订阅归属绑定用户, 普通用户只能修改或删除自己的订阅;未绑定或绑定用户已停用时拒绝执行。三项操作仍保留确认机制。 diff --git a/docs/refactor/agent-api-surface-audit.json b/docs/refactor/agent-api-surface-audit.json index 45cb9e171..6d444439c 100644 --- a/docs/refactor/agent-api-surface-audit.json +++ b/docs/refactor/agent-api-surface-audit.json @@ -19,6 +19,2318 @@ } ], "gateway_http_route_count": 229, + "gateway_identity_contracts": [ + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "config.identifiers.get", + "path": "/api/v1/system/identifiers", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "reversible_write", + "identity_scope": "admin", + "method": "POST", + "operation_id": "config.identifiers.update", + "path": "/api/v1/system/identifiers", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "config.public.get", + "path": "/api/v1/system/setting/public/{key}", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "config.system.describe", + "path": "/api/v1/system/settings/describe/{setting_key}", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "config.system.get", + "path": "/api/v1/system/settings", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "config.system.list", + "path": "/api/v1/system/settings/catalog", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "required", + "effect": "reversible_write", + "identity_scope": "admin", + "method": "POST", + "operation_id": "config.system.update", + "path": "/api/v1/system/settings", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "config.user.get", + "path": "/api/v1/system/global/user", + "required_role": "user", + "result_sensitivity": "private" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "dashboard.cpu", + "path": "/api/v1/dashboard/cpu", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "dashboard.downloader", + "path": "/api/v1/dashboard/downloader", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "dashboard.media.statistics", + "path": "/api/v1/dashboard/statistic", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "dashboard.memory", + "path": "/api/v1/dashboard/memory", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "dashboard.network", + "path": "/api/v1/dashboard/network", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "dashboard.processes", + "path": "/api/v1/dashboard/processes", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "dashboard.storage", + "path": "/api/v1/dashboard/storage", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "dashboard.system", + "path": "/api/v1/dashboard/system", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "dashboard.transfer.statistics", + "path": "/api/v1/dashboard/transfer", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "external_side_effect", + "identity_scope": "admin", + "method": "POST", + "operation_id": "database.backups.create", + "path": "/api/v1/system/database/backups", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "required", + "effect": "destructive_write", + "identity_scope": "admin", + "method": "DELETE", + "operation_id": "database.backups.delete", + "path": "/api/v1/system/database/backups/{name}", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "database.backups.list", + "path": "/api/v1/system/database/backups", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "POST", + "operation_id": "database.backups.verify", + "path": "/api/v1/system/database/backups/{name}/verify", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "required", + "effect": "external_side_effect", + "identity_scope": "bound_user", + "method": "POST", + "operation_id": "download.add", + "path": "/api/v1/download/add", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "external_side_effect", + "identity_scope": "bound_user", + "method": "POST", + "operation_id": "download.artist_collection", + "path": "/api/v1/download/artist-collection", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "download.clients", + "path": "/api/v1/download/clients", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "destructive_write", + "identity_scope": "admin", + "method": "DELETE", + "operation_id": "download.history.delete", + "path": "/api/v1/history/download", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "download.history.list", + "path": "/api/v1/history/download", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "download.paths", + "path": "/api/v1/download/paths", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "download.tasks.active", + "path": "/api/v1/download/", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "filter.builtin", + "path": "/api/v1/rule/builtin", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "filter.custom", + "path": "/api/v1/rule/custom", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "reversible_write", + "identity_scope": "admin", + "method": "POST", + "operation_id": "filter.custom.add", + "path": "/api/v1/rule/custom", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "destructive_write", + "identity_scope": "admin", + "method": "DELETE", + "operation_id": "filter.custom.delete", + "path": "/api/v1/rule/custom/{rule_id}", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "reversible_write", + "identity_scope": "admin", + "method": "PUT", + "operation_id": "filter.custom.update", + "path": "/api/v1/rule/custom/{rule_id}", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "reversible_write", + "identity_scope": "admin", + "method": "POST", + "operation_id": "filter.group.add", + "path": "/api/v1/rule/groups", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "destructive_write", + "identity_scope": "admin", + "method": "DELETE", + "operation_id": "filter.group.delete", + "path": "/api/v1/rule/groups/{name}", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "reversible_write", + "identity_scope": "admin", + "method": "PUT", + "operation_id": "filter.group.update", + "path": "/api/v1/rule/groups/{name}", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "filter.groups", + "path": "/api/v1/rule/groups", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "external_side_effect", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "filter.test", + "path": "/api/v1/system/ruletest", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "library.exists", + "path": "/api/v1/mediaserver/exists", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "library.latest", + "path": "/api/v1/mediaserver/latest", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "destructive_write", + "identity_scope": "admin", + "method": "DELETE", + "operation_id": "media.cache.clear", + "path": "/api/v1/tmdb/cache", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "destructive_write", + "identity_scope": "admin", + "method": "DELETE", + "operation_id": "media.cache.delete", + "path": "/api/v1/tmdb/cache/{cache_key}", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "media.cache.get", + "path": "/api/v1/tmdb/cache", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "media.classification.fields", + "path": "/api/v1/media/classification/fields", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "media.classification.policy.get", + "path": "/api/v1/media/classification/policy", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "media.classification.policy.history", + "path": "/api/v1/media/classification/history", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "POST", + "operation_id": "media.classification.policy.impact", + "path": "/api/v1/media/classification/impact", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "POST", + "operation_id": "media.classification.policy.preview", + "path": "/api/v1/media/classification/preview", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "reversible_write", + "identity_scope": "admin", + "method": "POST", + "operation_id": "media.classification.policy.rollback", + "path": "/api/v1/media/classification/rollback/{revision}", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "reversible_write", + "identity_scope": "admin", + "method": "PUT", + "operation_id": "media.classification.policy.update", + "path": "/api/v1/media/classification/policy", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "POST", + "operation_id": "media.classification.policy.validate", + "path": "/api/v1/media/classification/validate", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "public", + "method": "GET", + "operation_id": "media.detail", + "path": "/api/v1/media/{media_id}", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "public", + "method": "GET", + "operation_id": "media.episode_group.seasons", + "path": "/api/v1/media/group/seasons/{episode_group}", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "public", + "method": "GET", + "operation_id": "media.episode_groups", + "path": "/api/v1/media/groups/{tmdbid}", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "public", + "method": "GET", + "operation_id": "media.episode_schedule", + "path": "/api/v1/tmdb/{tmdbid}/{season}", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "public", + "method": "GET", + "operation_id": "media.person.credits", + "path": "/api/v1/{source}/person/credits/{person_id}", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "public", + "method": "GET", + "operation_id": "media.person.search", + "path": "/api/v1/media/search", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "media.recognize", + "path": "/api/v1/media/recognize", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "media.recognize_file", + "path": "/api/v1/media/recognize_file", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "external_side_effect", + "identity_scope": "admin", + "method": "POST", + "operation_id": "media.scrape", + "path": "/api/v1/media/scrape/{storage}", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "public", + "method": "GET", + "operation_id": "media.search", + "path": "/api/v1/media/search", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "public", + "method": "GET", + "operation_id": "media.seasons", + "path": "/api/v1/media/seasons", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "media.sources", + "path": "/api/v1/media/source", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "public", + "method": "GET", + "operation_id": "music.album.get", + "path": "/api/v1/music/album/{album_id}", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "public", + "method": "GET", + "operation_id": "music.album.related", + "path": "/api/v1/music/album/{album_id}/related", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "public", + "method": "GET", + "operation_id": "music.artist.albums", + "path": "/api/v1/music/artist/{artist_id}/albums", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "public", + "method": "GET", + "operation_id": "music.artist.get", + "path": "/api/v1/music/artist/{artist_id}", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "public", + "method": "GET", + "operation_id": "music.artist.related", + "path": "/api/v1/music/artist/{artist_id}/related", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "destructive_write", + "identity_scope": "admin", + "method": "DELETE", + "operation_id": "music.cache.clear", + "path": "/api/v1/music/cache", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "destructive_write", + "identity_scope": "admin", + "method": "DELETE", + "operation_id": "music.cache.delete", + "path": "/api/v1/music/cache/{cache_key}", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "music.cache.get", + "path": "/api/v1/music/cache", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "public", + "method": "GET", + "operation_id": "music.explore", + "path": "/api/v1/music/explore", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "public", + "method": "POST", + "operation_id": "music.recognize", + "path": "/api/v1/music/recognize", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "plugin.capabilities", + "path": "/api/v1/plugin/runtime/capabilities", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "external_side_effect", + "identity_scope": "admin", + "method": "POST", + "operation_id": "plugin.clone", + "path": "/api/v1/plugin/clone/{plugin_id}", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "plugin.clone.restorable", + "path": "/api/v1/plugin/clone/{plugin_id}/restorable", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "plugin.config.get", + "path": "/api/v1/plugin/form/{plugin_id}", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "required", + "effect": "reversible_write", + "identity_scope": "admin", + "method": "PUT", + "operation_id": "plugin.config.update", + "path": "/api/v1/plugin/{plugin_id}", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "plugin.data", + "path": "/api/v1/plugin/runtime/{plugin_id}/data", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "required", + "effect": "reversible_write", + "identity_scope": "admin", + "method": "DELETE", + "operation_id": "plugin.default_target.clear", + "path": "/api/v1/plugin/instances/{plugin_id}/{instance_id}/default_target", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "reversible_write", + "identity_scope": "admin", + "method": "PUT", + "operation_id": "plugin.default_target.set", + "path": "/api/v1/plugin/instances/{plugin_id}/{instance_id}/default_target", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "reversible_write", + "identity_scope": "admin", + "method": "POST", + "operation_id": "plugin.folder.create", + "path": "/api/v1/plugin/folders/{folder_name}", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "destructive_write", + "identity_scope": "admin", + "method": "DELETE", + "operation_id": "plugin.folder.delete", + "path": "/api/v1/plugin/folders/{folder_name}", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "reversible_write", + "identity_scope": "admin", + "method": "PUT", + "operation_id": "plugin.folder.plugin.assign", + "path": "/api/v1/plugin/folders/{folder_name}/plugins/{plugin_id}", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "reversible_write", + "identity_scope": "admin", + "method": "DELETE", + "operation_id": "plugin.folder.plugin.remove", + "path": "/api/v1/plugin/folders/{folder_name}/plugins/{plugin_id}", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "reversible_write", + "identity_scope": "admin", + "method": "PUT", + "operation_id": "plugin.folder.plugins.update", + "path": "/api/v1/plugin/folders/{folder_name}/plugins", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "reversible_write", + "identity_scope": "admin", + "method": "PATCH", + "operation_id": "plugin.folder.update", + "path": "/api/v1/plugin/folders/{folder_name}", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "plugin.folders.get", + "path": "/api/v1/plugin/folders", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "required", + "effect": "reversible_write", + "identity_scope": "admin", + "method": "POST", + "operation_id": "plugin.folders.update", + "path": "/api/v1/plugin/folders", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "plugin.history", + "path": "/api/v1/plugin/history/{plugin_id}", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "external_side_effect", + "identity_scope": "admin", + "method": "GET", + "operation_id": "plugin.install", + "path": "/api/v1/plugin/install/{plugin_id}", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "plugin.installed", + "path": "/api/v1/plugin/", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "destructive_write", + "identity_scope": "admin", + "method": "POST", + "operation_id": "plugin.instance.purge", + "path": "/api/v1/plugin/instance/{instance_id}/purge", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "reversible_write", + "identity_scope": "admin", + "method": "POST", + "operation_id": "plugin.instance.set_enabled", + "path": "/api/v1/plugin/instance/{instance_id}/enabled", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "reversible_write", + "identity_scope": "admin", + "method": "DELETE", + "operation_id": "plugin.loglevel.clear", + "path": "/api/v1/plugin/loglevel/{plugin_id}/{instance_id}", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "plugin.loglevel.get", + "path": "/api/v1/plugin/loglevel/{plugin_id}", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "required", + "effect": "reversible_write", + "identity_scope": "admin", + "method": "PUT", + "operation_id": "plugin.loglevel.set", + "path": "/api/v1/plugin/loglevel/{plugin_id}/{instance_id}", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "plugin.market", + "path": "/api/v1/plugin/", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "external_side_effect", + "identity_scope": "admin", + "method": "POST", + "operation_id": "plugin.market.sync_wiki", + "path": "/api/v1/system/setting/PLUGIN_MARKET/sync-wiki", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "plugin.rating", + "path": "/api/v1/plugin/rating/{plugin_id}", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "required", + "effect": "external_side_effect", + "identity_scope": "admin", + "method": "POST", + "operation_id": "plugin.rating.submit", + "path": "/api/v1/plugin/rating/{plugin_id}", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "plugin.ratings", + "path": "/api/v1/plugin/rating", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "plugin.releases", + "path": "/api/v1/plugin/releases/{plugin_id}", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "external_side_effect", + "identity_scope": "admin", + "method": "POST", + "operation_id": "plugin.reload", + "path": "/api/v1/plugin/reload/{plugin_id}", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "destructive_write", + "identity_scope": "admin", + "method": "GET", + "operation_id": "plugin.reset", + "path": "/api/v1/plugin/reset/{plugin_id}", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "plugin.runtime.status", + "path": "/api/v1/plugin/runtime", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "required", + "effect": "external_side_effect", + "identity_scope": "admin", + "method": "POST", + "operation_id": "plugin.source.change", + "path": "/api/v1/plugin/source/{plugin_id}", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "external_side_effect", + "identity_scope": "admin", + "method": "POST", + "operation_id": "plugin.source.install", + "path": "/api/v1/plugin/source/{plugin_id}/install", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "plugin.source.options", + "path": "/api/v1/plugin/source/{plugin_id}", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "public", + "method": "GET", + "operation_id": "plugin.statistics", + "path": "/api/v1/plugin/statistic", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "destructive_write", + "identity_scope": "admin", + "method": "DELETE", + "operation_id": "plugin.uninstall", + "path": "/api/v1/plugin/{plugin_id}", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "public", + "method": "GET", + "operation_id": "recommendation.list", + "path": "/api/v1/recommend/agent", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "scheduler.list", + "path": "/api/v1/dashboard/schedule", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "scheduler.progress", + "path": "/api/v1/dashboard/schedule/{job_id}/progress", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "external_side_effect", + "identity_scope": "admin", + "method": "GET", + "operation_id": "scheduler.run", + "path": "/api/v1/system/runscheduler", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "external_side_effect", + "identity_scope": "bound_user", + "method": "POST", + "operation_id": "search.recommend", + "path": "/api/v1/search/recommend", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "search.results", + "path": "/api/v1/search/last/context", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "external_side_effect", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "search.title", + "path": "/api/v1/search/title", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "search.torrents", + "path": "/api/v1/search/media/{media_id}", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "reversible_write", + "identity_scope": "admin", + "method": "POST", + "operation_id": "site.add", + "path": "/api/v1/site/", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "site.auth.options", + "path": "/api/v1/site/auth", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "external_side_effect", + "identity_scope": "admin", + "method": "POST", + "operation_id": "site.authenticate", + "path": "/api/v1/site/auth", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "site.category", + "path": "/api/v1/site/category/{site_id}", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "reversible_write", + "identity_scope": "admin", + "method": "POST", + "operation_id": "site.cookie.set", + "path": "/api/v1/site/cookie/{site_id}/set", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "required", + "effect": "reversible_write", + "identity_scope": "admin", + "method": "POST", + "operation_id": "site.cookie.update", + "path": "/api/v1/site/cookie/{site_id}", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "required", + "effect": "external_side_effect", + "identity_scope": "admin", + "method": "POST", + "operation_id": "site.cookiecloud.sync", + "path": "/api/v1/site/cookiecloud", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "destructive_write", + "identity_scope": "admin", + "method": "DELETE", + "operation_id": "site.delete", + "path": "/api/v1/site/{site_id}", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "site.list", + "path": "/api/v1/site/agent", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "site.mapping", + "path": "/api/v1/site/mapping", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "required", + "effect": "reversible_write", + "identity_scope": "admin", + "method": "POST", + "operation_id": "site.priorities.update", + "path": "/api/v1/site/priorities", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "destructive_write", + "identity_scope": "admin", + "method": "POST", + "operation_id": "site.reset", + "path": "/api/v1/site/reset", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "external_side_effect", + "identity_scope": "admin", + "method": "GET", + "operation_id": "site.resource", + "path": "/api/v1/site/resource/{site_id}", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "site.rss", + "path": "/api/v1/site/rss", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "site.searchable", + "path": "/api/v1/site/media/{media_type}", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "site.statistic", + "path": "/api/v1/site/statistic/{site_url}", + "required_role": "user", + "result_sensitivity": "private" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "site.statistics", + "path": "/api/v1/site/statistic", + "required_role": "user", + "result_sensitivity": "private" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "site.supporting", + "path": "/api/v1/site/supporting", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "site.test", + "path": "/api/v1/site/test/{site_id}", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "reversible_write", + "identity_scope": "admin", + "method": "PUT", + "operation_id": "site.update", + "path": "/api/v1/site/", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "site.userdata", + "path": "/api/v1/site/userdata/{site_id}", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "site.userdata.latest", + "path": "/api/v1/site/userdata/latest", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "required", + "effect": "external_side_effect", + "identity_scope": "admin", + "method": "POST", + "operation_id": "site.userdata.refresh", + "path": "/api/v1/site/userdata/{site_id}", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "slash.list", + "path": "/api/v1/message/agent/commands", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "external_side_effect", + "identity_scope": "admin", + "method": "POST", + "operation_id": "slash.run", + "path": "/api/v1/message/agent/commands/run", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "destructive_write", + "identity_scope": "admin", + "method": "POST", + "operation_id": "storage.delete", + "path": "/api/v1/storage/delete", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "POST", + "operation_id": "storage.list", + "path": "/api/v1/storage/agent/list", + "required_role": "user", + "result_sensitivity": "private" + }, + { + "confirmation": "required", + "effect": "external_side_effect", + "identity_scope": "admin", + "method": "POST", + "operation_id": "storage.manage", + "path": "/api/v1/storage/manage", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "required", + "effect": "reversible_write", + "identity_scope": "admin", + "method": "POST", + "operation_id": "storage.mkdir", + "path": "/api/v1/storage/mkdir", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "reversible_write", + "identity_scope": "admin", + "method": "POST", + "operation_id": "storage.rename", + "path": "/api/v1/storage/rename", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "storage.settings", + "path": "/api/v1/storage/directories", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "reversible_write", + "identity_scope": "bound_user", + "method": "POST", + "operation_id": "subscription.add", + "path": "/api/v1/subscribe/", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "destructive_write", + "identity_scope": "bound_user", + "method": "DELETE", + "operation_id": "subscription.delete", + "path": "/api/v1/subscribe/{subscribe_id}", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "destructive_write", + "identity_scope": "bound_user", + "method": "DELETE", + "operation_id": "subscription.delete_by_media", + "path": "/api/v1/subscribe/media/{media_id}", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "external_side_effect", + "identity_scope": "bound_user", + "method": "PUT", + "operation_id": "subscription.execution.cancel", + "path": "/api/v1/subscribe/execution/batches/{batch_id}/cancel", + "required_role": "user", + "result_sensitivity": "private" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "subscription.execution.get", + "path": "/api/v1/subscribe/execution/batches/{batch_id}", + "required_role": "user", + "result_sensitivity": "private" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "subscription.execution.list", + "path": "/api/v1/subscribe/execution/batches", + "required_role": "user", + "result_sensitivity": "private" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "subscription.files", + "path": "/api/v1/subscribe/files/{subscribe_id}", + "required_role": "user", + "result_sensitivity": "private" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "subscription.find", + "path": "/api/v1/subscribe/media/{media_id}", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "reversible_write", + "identity_scope": "bound_user", + "method": "POST", + "operation_id": "subscription.follow.add", + "path": "/api/v1/subscribe/follow", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "reversible_write", + "identity_scope": "bound_user", + "method": "DELETE", + "operation_id": "subscription.follow.delete", + "path": "/api/v1/subscribe/follow", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "subscription.follow.list", + "path": "/api/v1/subscribe/follow", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "external_side_effect", + "identity_scope": "bound_user", + "method": "POST", + "operation_id": "subscription.fork", + "path": "/api/v1/subscribe/fork", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "subscription.get", + "path": "/api/v1/subscribe/{subscribe_id}", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "subscription.history", + "path": "/api/v1/subscribe/history/{mtype}", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "destructive_write", + "identity_scope": "bound_user", + "method": "DELETE", + "operation_id": "subscription.history.delete", + "path": "/api/v1/subscribe/history/{history_id}", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "subscription.list", + "path": "/api/v1/subscribe/", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "external_side_effect", + "identity_scope": "admin", + "method": "POST", + "operation_id": "subscription.metadata.refresh", + "path": "/api/v1/subscribe/check", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "public", + "method": "GET", + "operation_id": "subscription.popular", + "path": "/api/v1/subscribe/popular", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "external_side_effect", + "identity_scope": "admin", + "method": "POST", + "operation_id": "subscription.refresh", + "path": "/api/v1/subscribe/refresh", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "reversible_write", + "identity_scope": "bound_user", + "method": "POST", + "operation_id": "subscription.reset", + "path": "/api/v1/subscribe/reset/{subid}", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "external_side_effect", + "identity_scope": "bound_user", + "method": "POST", + "operation_id": "subscription.search", + "path": "/api/v1/subscribe/search/{subscribe_id}", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "external_side_effect", + "identity_scope": "bound_user", + "method": "POST", + "operation_id": "subscription.search_all", + "path": "/api/v1/subscribe/search", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "external_side_effect", + "identity_scope": "bound_user", + "method": "POST", + "operation_id": "subscription.share", + "path": "/api/v1/subscribe/share", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "external_side_effect", + "identity_scope": "bound_user", + "method": "DELETE", + "operation_id": "subscription.share.delete", + "path": "/api/v1/subscribe/share/{share_id}", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "public", + "method": "GET", + "operation_id": "subscription.share.statistics", + "path": "/api/v1/subscribe/share/statistics", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "public", + "method": "GET", + "operation_id": "subscription.shares", + "path": "/api/v1/subscribe/shares", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "reversible_write", + "identity_scope": "bound_user", + "method": "PUT", + "operation_id": "subscription.status.update", + "path": "/api/v1/subscribe/status/{subid}", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "reversible_write", + "identity_scope": "bound_user", + "method": "PUT", + "operation_id": "subscription.update", + "path": "/api/v1/subscribe/", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "subscription.user.list", + "path": "/api/v1/subscribe/user/{username}", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "external_side_effect", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "subtitle.search.media", + "path": "/api/v1/search/subtitle/media/{media_id}", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "external_side_effect", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "subtitle.search.title", + "path": "/api/v1/search/subtitle/title", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "system.module.catalog", + "path": "/api/v1/system/module-catalog", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "system.module.list", + "path": "/api/v1/system/modulelist", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "system.module.settings", + "path": "/api/v1/system/module-settings", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "external_side_effect", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "system.module.test", + "path": "/api/v1/system/moduletest/{moduleid}", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "system.network.targets", + "path": "/api/v1/system/nettest/targets", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "external_side_effect", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "system.network.test", + "path": "/api/v1/system/nettest", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "external_side_effect", + "identity_scope": "admin", + "method": "GET", + "operation_id": "system.restart", + "path": "/api/v1/system/restart", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "external_side_effect", + "identity_scope": "admin", + "method": "POST", + "operation_id": "system.update.check", + "path": "/api/v1/system/update/check", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "external_side_effect", + "identity_scope": "admin", + "method": "POST", + "operation_id": "system.update.download", + "path": "/api/v1/system/update/download", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "external_side_effect", + "identity_scope": "admin", + "method": "POST", + "operation_id": "system.update.install", + "path": "/api/v1/system/update/install", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "system.update.status", + "path": "/api/v1/system/update/status", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "external_side_effect", + "identity_scope": "admin", + "method": "POST", + "operation_id": "system.upgrade.dev", + "path": "/api/v1/system/upgrade", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "system.usage.statistics", + "path": "/api/v1/system/usage/statistic", + "required_role": "user", + "result_sensitivity": "private" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "system.versions", + "path": "/api/v1/system/versions", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "destructive_write", + "identity_scope": "admin", + "method": "DELETE", + "operation_id": "torrent.cache.clear", + "path": "/api/v1/torrent/cache", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "destructive_write", + "identity_scope": "admin", + "method": "DELETE", + "operation_id": "torrent.cache.delete", + "path": "/api/v1/torrent/cache/{domain}/{torrent_hash}", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "torrent.cache.get", + "path": "/api/v1/torrent/cache", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "required", + "effect": "external_side_effect", + "identity_scope": "admin", + "method": "POST", + "operation_id": "torrent.cache.refresh", + "path": "/api/v1/torrent/cache/refresh", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "reversible_write", + "identity_scope": "admin", + "method": "POST", + "operation_id": "torrent.cache.reidentify", + "path": "/api/v1/torrent/cache/reidentify/{domain}/{torrent_hash}", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "POST", + "operation_id": "transfer.episode_format.recommend", + "path": "/api/v1/transfer/episode-format/recommend", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "required", + "effect": "external_side_effect", + "identity_scope": "admin", + "method": "POST", + "operation_id": "transfer.file", + "path": "/api/v1/transfer/manual", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "transfer.history", + "path": "/api/v1/history/transfer", + "required_role": "user", + "result_sensitivity": "private" + }, + { + "confirmation": "required", + "effect": "destructive_write", + "identity_scope": "admin", + "method": "DELETE", + "operation_id": "transfer.history.clear", + "path": "/api/v1/history/transfer/all", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "destructive_write", + "identity_scope": "admin", + "method": "DELETE", + "operation_id": "transfer.history.delete", + "path": "/api/v1/history/transfer", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "external_side_effect", + "identity_scope": "admin", + "method": "POST", + "operation_id": "transfer.history.redo", + "path": "/api/v1/history/transfer/{history_id}/ai-redo", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "external_side_effect", + "identity_scope": "admin", + "method": "POST", + "operation_id": "transfer.history.redo_batch", + "path": "/api/v1/history/transfer/ai-redo", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "POST", + "operation_id": "transfer.manual_history", + "path": "/api/v1/transfer/manual/history", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "transfer.manual_review", + "path": "/api/v1/transfer/tasks/{task_id}/manual-review", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "required", + "effect": "reversible_write", + "identity_scope": "admin", + "method": "POST", + "operation_id": "transfer.manual_review.resolve", + "path": "/api/v1/transfer/tasks/{task_id}/manual-review", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "transfer.manual_reviews", + "path": "/api/v1/transfer/tasks/manual-reviews", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "transfer.name", + "path": "/api/v1/transfer/name", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "transfer.queue", + "path": "/api/v1/transfer/queue", + "required_role": "user", + "result_sensitivity": "private" + }, + { + "confirmation": "required", + "effect": "destructive_write", + "identity_scope": "bound_user", + "method": "DELETE", + "operation_id": "transfer.queue.delete", + "path": "/api/v1/transfer/queue", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "POST", + "operation_id": "transfer.target_path", + "path": "/api/v1/transfer/manual/target-path", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "workflow.actions", + "path": "/api/v1/workflow/actions", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "reversible_write", + "identity_scope": "admin", + "method": "POST", + "operation_id": "workflow.create", + "path": "/api/v1/workflow/", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "destructive_write", + "identity_scope": "admin", + "method": "DELETE", + "operation_id": "workflow.delete", + "path": "/api/v1/workflow/{workflow_id}", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "workflow.event_types", + "path": "/api/v1/workflow/event_types", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "external_side_effect", + "identity_scope": "admin", + "method": "POST", + "operation_id": "workflow.fork", + "path": "/api/v1/workflow/fork", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "workflow.get", + "path": "/api/v1/workflow/{workflow_id}", + "required_role": "system_admin", + "result_sensitivity": "private" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "bound_user", + "method": "GET", + "operation_id": "workflow.list", + "path": "/api/v1/workflow/agent", + "required_role": "user", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "reversible_write", + "identity_scope": "admin", + "method": "POST", + "operation_id": "workflow.pause", + "path": "/api/v1/workflow/{workflow_id}/pause", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "workflow.plugin.actions", + "path": "/api/v1/workflow/plugin/actions", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "reversible_write", + "identity_scope": "admin", + "method": "POST", + "operation_id": "workflow.reset", + "path": "/api/v1/workflow/{workflow_id}/reset", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "external_side_effect", + "identity_scope": "admin", + "method": "POST", + "operation_id": "workflow.run", + "path": "/api/v1/workflow/{workflow_id}/run", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "external_side_effect", + "identity_scope": "admin", + "method": "POST", + "operation_id": "workflow.share", + "path": "/api/v1/workflow/share", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "external_side_effect", + "identity_scope": "admin", + "method": "DELETE", + "operation_id": "workflow.share.delete", + "path": "/api/v1/workflow/share/{share_id}", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "none", + "effect": "safe_read", + "identity_scope": "admin", + "method": "GET", + "operation_id": "workflow.shares", + "path": "/api/v1/workflow/shares", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "reversible_write", + "identity_scope": "admin", + "method": "POST", + "operation_id": "workflow.start", + "path": "/api/v1/workflow/{workflow_id}/start", + "required_role": "system_admin", + "result_sensitivity": "normal" + }, + { + "confirmation": "required", + "effect": "reversible_write", + "identity_scope": "admin", + "method": "PUT", + "operation_id": "workflow.update", + "path": "/api/v1/workflow/{workflow_id}", + "required_role": "system_admin", + "result_sensitivity": "normal" + } + ], "gateway_operation_count": 231, "matched_gateway_http_route_count": 228, "openapi_operation_count": 426, diff --git a/docs/refactor/agent-api-surface-audit.md b/docs/refactor/agent-api-surface-audit.md index 3e40d1fba..dd0989d7a 100644 --- a/docs/refactor/agent-api-surface-audit.md +++ b/docs/refactor/agent-api-surface-audit.md @@ -25,6 +25,249 @@ | `transport_or_identity` | 76 | Authentication, protocol, callback, account, or conversation transport boundary. | | `ui_presentation` | 15 | Frontend or plugin-rendered presentation contract. | +## Gateway Identity Audit + +All gateway operations are audited below. New operations default to bound_user; safe_read alone never grants public access. +public: audited external public metadata only; the host-authenticated executor cannot call other operations. +bound_user: an active account bound to the stable channel sender ID, or the authenticated Web user ID; endpoint ownership and management permissions still apply. +admin: administrator authority, preserving existing verified channel/API integration semantics; this never substitutes for ownership on bound_user operations. +Title recognition retains bound_user because its Context exposes locally configured applied identification rules. File recognition, source availability, rules, search results, media libraries, storage and public UI settings remain instance data. +Plugin ratings include this installation's own rating and require the same administrator role as their REST dependencies. Community share/popularity/statistics and global plugin installation statistics are public reads; follow lists and rating submissions are not. + +| operation | method | path | identity scope | role | effect | result sensitivity | confirmation | +| :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | +| `config.identifiers.get` | `GET` | `/api/v1/system/identifiers` | `admin` | `system_admin` | `safe_read` | `normal` | `none` | +| `config.identifiers.update` | `POST` | `/api/v1/system/identifiers` | `admin` | `system_admin` | `reversible_write` | `normal` | `required` | +| `config.public.get` | `GET` | `/api/v1/system/setting/public/{key}` | `bound_user` | `user` | `safe_read` | `normal` | `none` | +| `config.system.describe` | `GET` | `/api/v1/system/settings/describe/{setting_key}` | `admin` | `system_admin` | `safe_read` | `private` | `none` | +| `config.system.get` | `GET` | `/api/v1/system/settings` | `admin` | `system_admin` | `safe_read` | `private` | `none` | +| `config.system.list` | `GET` | `/api/v1/system/settings/catalog` | `admin` | `system_admin` | `safe_read` | `private` | `none` | +| `config.system.update` | `POST` | `/api/v1/system/settings` | `admin` | `system_admin` | `reversible_write` | `private` | `required` | +| `config.user.get` | `GET` | `/api/v1/system/global/user` | `bound_user` | `user` | `safe_read` | `private` | `none` | +| `dashboard.cpu` | `GET` | `/api/v1/dashboard/cpu` | `admin` | `system_admin` | `safe_read` | `normal` | `none` | +| `dashboard.downloader` | `GET` | `/api/v1/dashboard/downloader` | `admin` | `system_admin` | `safe_read` | `private` | `none` | +| `dashboard.media.statistics` | `GET` | `/api/v1/dashboard/statistic` | `admin` | `system_admin` | `safe_read` | `private` | `none` | +| `dashboard.memory` | `GET` | `/api/v1/dashboard/memory` | `admin` | `system_admin` | `safe_read` | `normal` | `none` | +| `dashboard.network` | `GET` | `/api/v1/dashboard/network` | `admin` | `system_admin` | `safe_read` | `normal` | `none` | +| `dashboard.processes` | `GET` | `/api/v1/dashboard/processes` | `admin` | `system_admin` | `safe_read` | `private` | `none` | +| `dashboard.storage` | `GET` | `/api/v1/dashboard/storage` | `admin` | `system_admin` | `safe_read` | `private` | `none` | +| `dashboard.system` | `GET` | `/api/v1/dashboard/system` | `admin` | `system_admin` | `safe_read` | `private` | `none` | +| `dashboard.transfer.statistics` | `GET` | `/api/v1/dashboard/transfer` | `admin` | `system_admin` | `safe_read` | `normal` | `none` | +| `database.backups.create` | `POST` | `/api/v1/system/database/backups` | `admin` | `system_admin` | `external_side_effect` | `private` | `required` | +| `database.backups.delete` | `DELETE` | `/api/v1/system/database/backups/{name}` | `admin` | `system_admin` | `destructive_write` | `normal` | `required` | +| `database.backups.list` | `GET` | `/api/v1/system/database/backups` | `admin` | `system_admin` | `safe_read` | `private` | `none` | +| `database.backups.verify` | `POST` | `/api/v1/system/database/backups/{name}/verify` | `admin` | `system_admin` | `safe_read` | `private` | `none` | +| `download.add` | `POST` | `/api/v1/download/add` | `bound_user` | `user` | `external_side_effect` | `normal` | `required` | +| `download.artist_collection` | `POST` | `/api/v1/download/artist-collection` | `bound_user` | `user` | `external_side_effect` | `normal` | `required` | +| `download.clients` | `GET` | `/api/v1/download/clients` | `bound_user` | `user` | `safe_read` | `normal` | `none` | +| `download.history.delete` | `DELETE` | `/api/v1/history/download` | `admin` | `system_admin` | `destructive_write` | `normal` | `required` | +| `download.history.list` | `GET` | `/api/v1/history/download` | `bound_user` | `user` | `safe_read` | `normal` | `none` | +| `download.paths` | `GET` | `/api/v1/download/paths` | `bound_user` | `user` | `safe_read` | `normal` | `none` | +| `download.tasks.active` | `GET` | `/api/v1/download/` | `bound_user` | `user` | `safe_read` | `normal` | `none` | +| `filter.builtin` | `GET` | `/api/v1/rule/builtin` | `bound_user` | `user` | `safe_read` | `normal` | `none` | +| `filter.custom` | `GET` | `/api/v1/rule/custom` | `bound_user` | `user` | `safe_read` | `normal` | `none` | +| `filter.custom.add` | `POST` | `/api/v1/rule/custom` | `admin` | `system_admin` | `reversible_write` | `normal` | `required` | +| `filter.custom.delete` | `DELETE` | `/api/v1/rule/custom/{rule_id}` | `admin` | `system_admin` | `destructive_write` | `normal` | `required` | +| `filter.custom.update` | `PUT` | `/api/v1/rule/custom/{rule_id}` | `admin` | `system_admin` | `reversible_write` | `normal` | `required` | +| `filter.group.add` | `POST` | `/api/v1/rule/groups` | `admin` | `system_admin` | `reversible_write` | `normal` | `required` | +| `filter.group.delete` | `DELETE` | `/api/v1/rule/groups/{name}` | `admin` | `system_admin` | `destructive_write` | `normal` | `required` | +| `filter.group.update` | `PUT` | `/api/v1/rule/groups/{name}` | `admin` | `system_admin` | `reversible_write` | `normal` | `required` | +| `filter.groups` | `GET` | `/api/v1/rule/groups` | `bound_user` | `user` | `safe_read` | `normal` | `none` | +| `filter.test` | `GET` | `/api/v1/system/ruletest` | `bound_user` | `user` | `external_side_effect` | `normal` | `none` | +| `library.exists` | `GET` | `/api/v1/mediaserver/exists` | `bound_user` | `user` | `safe_read` | `normal` | `none` | +| `library.latest` | `GET` | `/api/v1/mediaserver/latest` | `bound_user` | `user` | `safe_read` | `normal` | `none` | +| `media.cache.clear` | `DELETE` | `/api/v1/tmdb/cache` | `admin` | `system_admin` | `destructive_write` | `normal` | `required` | +| `media.cache.delete` | `DELETE` | `/api/v1/tmdb/cache/{cache_key}` | `admin` | `system_admin` | `destructive_write` | `normal` | `required` | +| `media.cache.get` | `GET` | `/api/v1/tmdb/cache` | `admin` | `system_admin` | `safe_read` | `private` | `none` | +| `media.classification.fields` | `GET` | `/api/v1/media/classification/fields` | `bound_user` | `user` | `safe_read` | `normal` | `none` | +| `media.classification.policy.get` | `GET` | `/api/v1/media/classification/policy` | `bound_user` | `user` | `safe_read` | `normal` | `none` | +| `media.classification.policy.history` | `GET` | `/api/v1/media/classification/history` | `admin` | `system_admin` | `safe_read` | `private` | `none` | +| `media.classification.policy.impact` | `POST` | `/api/v1/media/classification/impact` | `admin` | `system_admin` | `safe_read` | `private` | `none` | +| `media.classification.policy.preview` | `POST` | `/api/v1/media/classification/preview` | `bound_user` | `user` | `safe_read` | `normal` | `none` | +| `media.classification.policy.rollback` | `POST` | `/api/v1/media/classification/rollback/{revision}` | `admin` | `system_admin` | `reversible_write` | `normal` | `required` | +| `media.classification.policy.update` | `PUT` | `/api/v1/media/classification/policy` | `admin` | `system_admin` | `reversible_write` | `normal` | `required` | +| `media.classification.policy.validate` | `POST` | `/api/v1/media/classification/validate` | `admin` | `system_admin` | `safe_read` | `normal` | `none` | +| `media.detail` | `GET` | `/api/v1/media/{media_id}` | `public` | `user` | `safe_read` | `normal` | `none` | +| `media.episode_group.seasons` | `GET` | `/api/v1/media/group/seasons/{episode_group}` | `public` | `user` | `safe_read` | `normal` | `none` | +| `media.episode_groups` | `GET` | `/api/v1/media/groups/{tmdbid}` | `public` | `user` | `safe_read` | `normal` | `none` | +| `media.episode_schedule` | `GET` | `/api/v1/tmdb/{tmdbid}/{season}` | `public` | `user` | `safe_read` | `normal` | `none` | +| `media.person.credits` | `GET` | `/api/v1/{source}/person/credits/{person_id}` | `public` | `user` | `safe_read` | `normal` | `none` | +| `media.person.search` | `GET` | `/api/v1/media/search` | `public` | `user` | `safe_read` | `normal` | `none` | +| `media.recognize` | `GET` | `/api/v1/media/recognize` | `bound_user` | `user` | `safe_read` | `normal` | `none` | +| `media.recognize_file` | `GET` | `/api/v1/media/recognize_file` | `bound_user` | `user` | `safe_read` | `normal` | `none` | +| `media.scrape` | `POST` | `/api/v1/media/scrape/{storage}` | `admin` | `system_admin` | `external_side_effect` | `normal` | `required` | +| `media.search` | `GET` | `/api/v1/media/search` | `public` | `user` | `safe_read` | `normal` | `none` | +| `media.seasons` | `GET` | `/api/v1/media/seasons` | `public` | `user` | `safe_read` | `normal` | `none` | +| `media.sources` | `GET` | `/api/v1/media/source` | `bound_user` | `user` | `safe_read` | `normal` | `none` | +| `music.album.get` | `GET` | `/api/v1/music/album/{album_id}` | `public` | `user` | `safe_read` | `normal` | `none` | +| `music.album.related` | `GET` | `/api/v1/music/album/{album_id}/related` | `public` | `user` | `safe_read` | `normal` | `none` | +| `music.artist.albums` | `GET` | `/api/v1/music/artist/{artist_id}/albums` | `public` | `user` | `safe_read` | `normal` | `none` | +| `music.artist.get` | `GET` | `/api/v1/music/artist/{artist_id}` | `public` | `user` | `safe_read` | `normal` | `none` | +| `music.artist.related` | `GET` | `/api/v1/music/artist/{artist_id}/related` | `public` | `user` | `safe_read` | `normal` | `none` | +| `music.cache.clear` | `DELETE` | `/api/v1/music/cache` | `admin` | `system_admin` | `destructive_write` | `normal` | `required` | +| `music.cache.delete` | `DELETE` | `/api/v1/music/cache/{cache_key}` | `admin` | `system_admin` | `destructive_write` | `normal` | `required` | +| `music.cache.get` | `GET` | `/api/v1/music/cache` | `admin` | `system_admin` | `safe_read` | `private` | `none` | +| `music.explore` | `GET` | `/api/v1/music/explore` | `public` | `user` | `safe_read` | `normal` | `none` | +| `music.recognize` | `POST` | `/api/v1/music/recognize` | `public` | `user` | `safe_read` | `normal` | `none` | +| `plugin.capabilities` | `GET` | `/api/v1/plugin/runtime/capabilities` | `admin` | `system_admin` | `safe_read` | `normal` | `none` | +| `plugin.clone` | `POST` | `/api/v1/plugin/clone/{plugin_id}` | `admin` | `system_admin` | `external_side_effect` | `normal` | `required` | +| `plugin.clone.restorable` | `GET` | `/api/v1/plugin/clone/{plugin_id}/restorable` | `admin` | `system_admin` | `safe_read` | `private` | `none` | +| `plugin.config.get` | `GET` | `/api/v1/plugin/form/{plugin_id}` | `admin` | `system_admin` | `safe_read` | `private` | `none` | +| `plugin.config.update` | `PUT` | `/api/v1/plugin/{plugin_id}` | `admin` | `system_admin` | `reversible_write` | `private` | `required` | +| `plugin.data` | `GET` | `/api/v1/plugin/runtime/{plugin_id}/data` | `admin` | `system_admin` | `safe_read` | `private` | `none` | +| `plugin.default_target.clear` | `DELETE` | `/api/v1/plugin/instances/{plugin_id}/{instance_id}/default_target` | `admin` | `system_admin` | `reversible_write` | `normal` | `required` | +| `plugin.default_target.set` | `PUT` | `/api/v1/plugin/instances/{plugin_id}/{instance_id}/default_target` | `admin` | `system_admin` | `reversible_write` | `normal` | `required` | +| `plugin.folder.create` | `POST` | `/api/v1/plugin/folders/{folder_name}` | `admin` | `system_admin` | `reversible_write` | `normal` | `required` | +| `plugin.folder.delete` | `DELETE` | `/api/v1/plugin/folders/{folder_name}` | `admin` | `system_admin` | `destructive_write` | `normal` | `required` | +| `plugin.folder.plugin.assign` | `PUT` | `/api/v1/plugin/folders/{folder_name}/plugins/{plugin_id}` | `admin` | `system_admin` | `reversible_write` | `normal` | `required` | +| `plugin.folder.plugin.remove` | `DELETE` | `/api/v1/plugin/folders/{folder_name}/plugins/{plugin_id}` | `admin` | `system_admin` | `reversible_write` | `normal` | `required` | +| `plugin.folder.plugins.update` | `PUT` | `/api/v1/plugin/folders/{folder_name}/plugins` | `admin` | `system_admin` | `reversible_write` | `normal` | `required` | +| `plugin.folder.update` | `PATCH` | `/api/v1/plugin/folders/{folder_name}` | `admin` | `system_admin` | `reversible_write` | `normal` | `required` | +| `plugin.folders.get` | `GET` | `/api/v1/plugin/folders` | `admin` | `system_admin` | `safe_read` | `private` | `none` | +| `plugin.folders.update` | `POST` | `/api/v1/plugin/folders` | `admin` | `system_admin` | `reversible_write` | `normal` | `required` | +| `plugin.history` | `GET` | `/api/v1/plugin/history/{plugin_id}` | `admin` | `system_admin` | `safe_read` | `normal` | `none` | +| `plugin.install` | `GET` | `/api/v1/plugin/install/{plugin_id}` | `admin` | `system_admin` | `external_side_effect` | `normal` | `required` | +| `plugin.installed` | `GET` | `/api/v1/plugin/` | `admin` | `system_admin` | `safe_read` | `normal` | `none` | +| `plugin.instance.purge` | `POST` | `/api/v1/plugin/instance/{instance_id}/purge` | `admin` | `system_admin` | `destructive_write` | `normal` | `required` | +| `plugin.instance.set_enabled` | `POST` | `/api/v1/plugin/instance/{instance_id}/enabled` | `admin` | `system_admin` | `reversible_write` | `normal` | `required` | +| `plugin.loglevel.clear` | `DELETE` | `/api/v1/plugin/loglevel/{plugin_id}/{instance_id}` | `admin` | `system_admin` | `reversible_write` | `normal` | `required` | +| `plugin.loglevel.get` | `GET` | `/api/v1/plugin/loglevel/{plugin_id}` | `admin` | `system_admin` | `safe_read` | `private` | `none` | +| `plugin.loglevel.set` | `PUT` | `/api/v1/plugin/loglevel/{plugin_id}/{instance_id}` | `admin` | `system_admin` | `reversible_write` | `normal` | `required` | +| `plugin.market` | `GET` | `/api/v1/plugin/` | `admin` | `system_admin` | `safe_read` | `normal` | `none` | +| `plugin.market.sync_wiki` | `POST` | `/api/v1/system/setting/PLUGIN_MARKET/sync-wiki` | `admin` | `system_admin` | `external_side_effect` | `normal` | `required` | +| `plugin.rating` | `GET` | `/api/v1/plugin/rating/{plugin_id}` | `admin` | `system_admin` | `safe_read` | `private` | `none` | +| `plugin.rating.submit` | `POST` | `/api/v1/plugin/rating/{plugin_id}` | `admin` | `system_admin` | `external_side_effect` | `private` | `required` | +| `plugin.ratings` | `GET` | `/api/v1/plugin/rating` | `admin` | `system_admin` | `safe_read` | `private` | `none` | +| `plugin.releases` | `GET` | `/api/v1/plugin/releases/{plugin_id}` | `admin` | `system_admin` | `safe_read` | `normal` | `none` | +| `plugin.reload` | `POST` | `/api/v1/plugin/reload/{plugin_id}` | `admin` | `system_admin` | `external_side_effect` | `normal` | `required` | +| `plugin.reset` | `GET` | `/api/v1/plugin/reset/{plugin_id}` | `admin` | `system_admin` | `destructive_write` | `normal` | `required` | +| `plugin.runtime.status` | `GET` | `/api/v1/plugin/runtime` | `admin` | `system_admin` | `safe_read` | `private` | `none` | +| `plugin.source.change` | `POST` | `/api/v1/plugin/source/{plugin_id}` | `admin` | `system_admin` | `external_side_effect` | `normal` | `required` | +| `plugin.source.install` | `POST` | `/api/v1/plugin/source/{plugin_id}/install` | `admin` | `system_admin` | `external_side_effect` | `normal` | `required` | +| `plugin.source.options` | `GET` | `/api/v1/plugin/source/{plugin_id}` | `admin` | `system_admin` | `safe_read` | `normal` | `none` | +| `plugin.statistics` | `GET` | `/api/v1/plugin/statistic` | `public` | `user` | `safe_read` | `normal` | `none` | +| `plugin.uninstall` | `DELETE` | `/api/v1/plugin/{plugin_id}` | `admin` | `system_admin` | `destructive_write` | `normal` | `required` | +| `recommendation.list` | `GET` | `/api/v1/recommend/agent` | `public` | `user` | `safe_read` | `normal` | `none` | +| `scheduler.list` | `GET` | `/api/v1/dashboard/schedule` | `admin` | `system_admin` | `safe_read` | `normal` | `none` | +| `scheduler.progress` | `GET` | `/api/v1/dashboard/schedule/{job_id}/progress` | `admin` | `system_admin` | `safe_read` | `normal` | `none` | +| `scheduler.run` | `GET` | `/api/v1/system/runscheduler` | `admin` | `system_admin` | `external_side_effect` | `normal` | `required` | +| `search.recommend` | `POST` | `/api/v1/search/recommend` | `bound_user` | `user` | `external_side_effect` | `normal` | `none` | +| `search.results` | `GET` | `/api/v1/search/last/context` | `bound_user` | `user` | `safe_read` | `normal` | `none` | +| `search.title` | `GET` | `/api/v1/search/title` | `bound_user` | `user` | `external_side_effect` | `normal` | `none` | +| `search.torrents` | `GET` | `/api/v1/search/media/{media_id}` | `bound_user` | `user` | `safe_read` | `normal` | `none` | +| `site.add` | `POST` | `/api/v1/site/` | `admin` | `system_admin` | `reversible_write` | `private` | `required` | +| `site.auth.options` | `GET` | `/api/v1/site/auth` | `bound_user` | `user` | `safe_read` | `normal` | `none` | +| `site.authenticate` | `POST` | `/api/v1/site/auth` | `admin` | `system_admin` | `external_side_effect` | `private` | `required` | +| `site.category` | `GET` | `/api/v1/site/category/{site_id}` | `bound_user` | `user` | `safe_read` | `normal` | `none` | +| `site.cookie.set` | `POST` | `/api/v1/site/cookie/{site_id}/set` | `admin` | `system_admin` | `reversible_write` | `private` | `required` | +| `site.cookie.update` | `POST` | `/api/v1/site/cookie/{site_id}` | `admin` | `system_admin` | `reversible_write` | `private` | `required` | +| `site.cookiecloud.sync` | `POST` | `/api/v1/site/cookiecloud` | `admin` | `system_admin` | `external_side_effect` | `normal` | `required` | +| `site.delete` | `DELETE` | `/api/v1/site/{site_id}` | `admin` | `system_admin` | `destructive_write` | `normal` | `required` | +| `site.list` | `GET` | `/api/v1/site/agent` | `bound_user` | `user` | `safe_read` | `normal` | `none` | +| `site.mapping` | `GET` | `/api/v1/site/mapping` | `admin` | `system_admin` | `safe_read` | `private` | `none` | +| `site.priorities.update` | `POST` | `/api/v1/site/priorities` | `admin` | `system_admin` | `reversible_write` | `normal` | `required` | +| `site.reset` | `POST` | `/api/v1/site/reset` | `admin` | `system_admin` | `destructive_write` | `normal` | `required` | +| `site.resource` | `GET` | `/api/v1/site/resource/{site_id}` | `admin` | `system_admin` | `external_side_effect` | `private` | `none` | +| `site.rss` | `GET` | `/api/v1/site/rss` | `admin` | `system_admin` | `safe_read` | `private` | `none` | +| `site.searchable` | `GET` | `/api/v1/site/media/{media_type}` | `admin` | `system_admin` | `safe_read` | `private` | `none` | +| `site.statistic` | `GET` | `/api/v1/site/statistic/{site_url}` | `bound_user` | `user` | `safe_read` | `private` | `none` | +| `site.statistics` | `GET` | `/api/v1/site/statistic` | `bound_user` | `user` | `safe_read` | `private` | `none` | +| `site.supporting` | `GET` | `/api/v1/site/supporting` | `admin` | `system_admin` | `safe_read` | `normal` | `none` | +| `site.test` | `GET` | `/api/v1/site/test/{site_id}` | `bound_user` | `user` | `safe_read` | `normal` | `none` | +| `site.update` | `PUT` | `/api/v1/site/` | `admin` | `system_admin` | `reversible_write` | `normal` | `required` | +| `site.userdata` | `GET` | `/api/v1/site/userdata/{site_id}` | `admin` | `system_admin` | `safe_read` | `private` | `none` | +| `site.userdata.latest` | `GET` | `/api/v1/site/userdata/latest` | `admin` | `system_admin` | `safe_read` | `private` | `none` | +| `site.userdata.refresh` | `POST` | `/api/v1/site/userdata/{site_id}` | `admin` | `system_admin` | `external_side_effect` | `private` | `required` | +| `slash.list` | `GET` | `/api/v1/message/agent/commands` | `admin` | `system_admin` | `safe_read` | `normal` | `none` | +| `slash.run` | `POST` | `/api/v1/message/agent/commands/run` | `admin` | `system_admin` | `external_side_effect` | `normal` | `required` | +| `storage.delete` | `POST` | `/api/v1/storage/delete` | `admin` | `system_admin` | `destructive_write` | `normal` | `required` | +| `storage.list` | `POST` | `/api/v1/storage/agent/list` | `bound_user` | `user` | `safe_read` | `private` | `none` | +| `storage.manage` | `POST` | `/api/v1/storage/manage` | `admin` | `system_admin` | `external_side_effect` | `private` | `required` | +| `storage.mkdir` | `POST` | `/api/v1/storage/mkdir` | `admin` | `system_admin` | `reversible_write` | `normal` | `required` | +| `storage.rename` | `POST` | `/api/v1/storage/rename` | `admin` | `system_admin` | `reversible_write` | `normal` | `required` | +| `storage.settings` | `GET` | `/api/v1/storage/directories` | `admin` | `system_admin` | `safe_read` | `normal` | `none` | +| `subscription.add` | `POST` | `/api/v1/subscribe/` | `bound_user` | `user` | `reversible_write` | `normal` | `required` | +| `subscription.delete` | `DELETE` | `/api/v1/subscribe/{subscribe_id}` | `bound_user` | `user` | `destructive_write` | `normal` | `required` | +| `subscription.delete_by_media` | `DELETE` | `/api/v1/subscribe/media/{media_id}` | `bound_user` | `user` | `destructive_write` | `normal` | `required` | +| `subscription.execution.cancel` | `PUT` | `/api/v1/subscribe/execution/batches/{batch_id}/cancel` | `bound_user` | `user` | `external_side_effect` | `private` | `required` | +| `subscription.execution.get` | `GET` | `/api/v1/subscribe/execution/batches/{batch_id}` | `bound_user` | `user` | `safe_read` | `private` | `none` | +| `subscription.execution.list` | `GET` | `/api/v1/subscribe/execution/batches` | `bound_user` | `user` | `safe_read` | `private` | `none` | +| `subscription.files` | `GET` | `/api/v1/subscribe/files/{subscribe_id}` | `bound_user` | `user` | `safe_read` | `private` | `none` | +| `subscription.find` | `GET` | `/api/v1/subscribe/media/{media_id}` | `bound_user` | `user` | `safe_read` | `normal` | `none` | +| `subscription.follow.add` | `POST` | `/api/v1/subscribe/follow` | `bound_user` | `user` | `reversible_write` | `normal` | `required` | +| `subscription.follow.delete` | `DELETE` | `/api/v1/subscribe/follow` | `bound_user` | `user` | `reversible_write` | `normal` | `required` | +| `subscription.follow.list` | `GET` | `/api/v1/subscribe/follow` | `bound_user` | `user` | `safe_read` | `normal` | `none` | +| `subscription.fork` | `POST` | `/api/v1/subscribe/fork` | `bound_user` | `user` | `external_side_effect` | `normal` | `required` | +| `subscription.get` | `GET` | `/api/v1/subscribe/{subscribe_id}` | `bound_user` | `user` | `safe_read` | `normal` | `none` | +| `subscription.history` | `GET` | `/api/v1/subscribe/history/{mtype}` | `bound_user` | `user` | `safe_read` | `normal` | `none` | +| `subscription.history.delete` | `DELETE` | `/api/v1/subscribe/history/{history_id}` | `bound_user` | `user` | `destructive_write` | `normal` | `required` | +| `subscription.list` | `GET` | `/api/v1/subscribe/` | `bound_user` | `user` | `safe_read` | `normal` | `none` | +| `subscription.metadata.refresh` | `POST` | `/api/v1/subscribe/check` | `admin` | `system_admin` | `external_side_effect` | `normal` | `required` | +| `subscription.popular` | `GET` | `/api/v1/subscribe/popular` | `public` | `user` | `safe_read` | `normal` | `none` | +| `subscription.refresh` | `POST` | `/api/v1/subscribe/refresh` | `admin` | `system_admin` | `external_side_effect` | `normal` | `required` | +| `subscription.reset` | `POST` | `/api/v1/subscribe/reset/{subid}` | `bound_user` | `user` | `reversible_write` | `normal` | `required` | +| `subscription.search` | `POST` | `/api/v1/subscribe/search/{subscribe_id}` | `bound_user` | `user` | `external_side_effect` | `normal` | `required` | +| `subscription.search_all` | `POST` | `/api/v1/subscribe/search` | `bound_user` | `user` | `external_side_effect` | `normal` | `required` | +| `subscription.share` | `POST` | `/api/v1/subscribe/share` | `bound_user` | `user` | `external_side_effect` | `normal` | `required` | +| `subscription.share.delete` | `DELETE` | `/api/v1/subscribe/share/{share_id}` | `bound_user` | `user` | `external_side_effect` | `normal` | `required` | +| `subscription.share.statistics` | `GET` | `/api/v1/subscribe/share/statistics` | `public` | `user` | `safe_read` | `normal` | `none` | +| `subscription.shares` | `GET` | `/api/v1/subscribe/shares` | `public` | `user` | `safe_read` | `normal` | `none` | +| `subscription.status.update` | `PUT` | `/api/v1/subscribe/status/{subid}` | `bound_user` | `user` | `reversible_write` | `normal` | `required` | +| `subscription.update` | `PUT` | `/api/v1/subscribe/` | `bound_user` | `user` | `reversible_write` | `normal` | `required` | +| `subscription.user.list` | `GET` | `/api/v1/subscribe/user/{username}` | `bound_user` | `user` | `safe_read` | `normal` | `none` | +| `subtitle.search.media` | `GET` | `/api/v1/search/subtitle/media/{media_id}` | `bound_user` | `user` | `external_side_effect` | `normal` | `none` | +| `subtitle.search.title` | `GET` | `/api/v1/search/subtitle/title` | `bound_user` | `user` | `external_side_effect` | `normal` | `none` | +| `system.module.catalog` | `GET` | `/api/v1/system/module-catalog` | `bound_user` | `user` | `safe_read` | `normal` | `none` | +| `system.module.list` | `GET` | `/api/v1/system/modulelist` | `bound_user` | `user` | `safe_read` | `normal` | `none` | +| `system.module.settings` | `GET` | `/api/v1/system/module-settings` | `admin` | `system_admin` | `safe_read` | `normal` | `none` | +| `system.module.test` | `GET` | `/api/v1/system/moduletest/{moduleid}` | `bound_user` | `user` | `external_side_effect` | `normal` | `none` | +| `system.network.targets` | `GET` | `/api/v1/system/nettest/targets` | `bound_user` | `user` | `safe_read` | `normal` | `none` | +| `system.network.test` | `GET` | `/api/v1/system/nettest` | `bound_user` | `user` | `external_side_effect` | `normal` | `none` | +| `system.restart` | `GET` | `/api/v1/system/restart` | `admin` | `system_admin` | `external_side_effect` | `normal` | `required` | +| `system.update.check` | `POST` | `/api/v1/system/update/check` | `admin` | `system_admin` | `external_side_effect` | `normal` | `none` | +| `system.update.download` | `POST` | `/api/v1/system/update/download` | `admin` | `system_admin` | `external_side_effect` | `normal` | `required` | +| `system.update.install` | `POST` | `/api/v1/system/update/install` | `admin` | `system_admin` | `external_side_effect` | `normal` | `required` | +| `system.update.status` | `GET` | `/api/v1/system/update/status` | `admin` | `system_admin` | `safe_read` | `normal` | `none` | +| `system.upgrade.dev` | `POST` | `/api/v1/system/upgrade` | `admin` | `system_admin` | `external_side_effect` | `normal` | `required` | +| `system.usage.statistics` | `GET` | `/api/v1/system/usage/statistic` | `bound_user` | `user` | `safe_read` | `private` | `none` | +| `system.versions` | `GET` | `/api/v1/system/versions` | `admin` | `system_admin` | `safe_read` | `normal` | `none` | +| `torrent.cache.clear` | `DELETE` | `/api/v1/torrent/cache` | `admin` | `system_admin` | `destructive_write` | `normal` | `required` | +| `torrent.cache.delete` | `DELETE` | `/api/v1/torrent/cache/{domain}/{torrent_hash}` | `admin` | `system_admin` | `destructive_write` | `normal` | `required` | +| `torrent.cache.get` | `GET` | `/api/v1/torrent/cache` | `admin` | `system_admin` | `safe_read` | `private` | `none` | +| `torrent.cache.refresh` | `POST` | `/api/v1/torrent/cache/refresh` | `admin` | `system_admin` | `external_side_effect` | `normal` | `required` | +| `torrent.cache.reidentify` | `POST` | `/api/v1/torrent/cache/reidentify/{domain}/{torrent_hash}` | `admin` | `system_admin` | `reversible_write` | `normal` | `required` | +| `transfer.episode_format.recommend` | `POST` | `/api/v1/transfer/episode-format/recommend` | `admin` | `system_admin` | `safe_read` | `private` | `none` | +| `transfer.file` | `POST` | `/api/v1/transfer/manual` | `admin` | `system_admin` | `external_side_effect` | `normal` | `required` | +| `transfer.history` | `GET` | `/api/v1/history/transfer` | `bound_user` | `user` | `safe_read` | `private` | `none` | +| `transfer.history.clear` | `DELETE` | `/api/v1/history/transfer/all` | `admin` | `system_admin` | `destructive_write` | `normal` | `required` | +| `transfer.history.delete` | `DELETE` | `/api/v1/history/transfer` | `admin` | `system_admin` | `destructive_write` | `normal` | `required` | +| `transfer.history.redo` | `POST` | `/api/v1/history/transfer/{history_id}/ai-redo` | `admin` | `system_admin` | `external_side_effect` | `normal` | `required` | +| `transfer.history.redo_batch` | `POST` | `/api/v1/history/transfer/ai-redo` | `admin` | `system_admin` | `external_side_effect` | `normal` | `required` | +| `transfer.manual_history` | `POST` | `/api/v1/transfer/manual/history` | `admin` | `system_admin` | `safe_read` | `private` | `none` | +| `transfer.manual_review` | `GET` | `/api/v1/transfer/tasks/{task_id}/manual-review` | `admin` | `system_admin` | `safe_read` | `private` | `none` | +| `transfer.manual_review.resolve` | `POST` | `/api/v1/transfer/tasks/{task_id}/manual-review` | `admin` | `system_admin` | `reversible_write` | `normal` | `required` | +| `transfer.manual_reviews` | `GET` | `/api/v1/transfer/tasks/manual-reviews` | `admin` | `system_admin` | `safe_read` | `private` | `none` | +| `transfer.name` | `GET` | `/api/v1/transfer/name` | `admin` | `system_admin` | `safe_read` | `normal` | `none` | +| `transfer.queue` | `GET` | `/api/v1/transfer/queue` | `bound_user` | `user` | `safe_read` | `private` | `none` | +| `transfer.queue.delete` | `DELETE` | `/api/v1/transfer/queue` | `bound_user` | `user` | `destructive_write` | `normal` | `required` | +| `transfer.target_path` | `POST` | `/api/v1/transfer/manual/target-path` | `admin` | `system_admin` | `safe_read` | `private` | `none` | +| `workflow.actions` | `GET` | `/api/v1/workflow/actions` | `admin` | `system_admin` | `safe_read` | `normal` | `none` | +| `workflow.create` | `POST` | `/api/v1/workflow/` | `admin` | `system_admin` | `reversible_write` | `normal` | `required` | +| `workflow.delete` | `DELETE` | `/api/v1/workflow/{workflow_id}` | `admin` | `system_admin` | `destructive_write` | `normal` | `required` | +| `workflow.event_types` | `GET` | `/api/v1/workflow/event_types` | `admin` | `system_admin` | `safe_read` | `normal` | `none` | +| `workflow.fork` | `POST` | `/api/v1/workflow/fork` | `admin` | `system_admin` | `external_side_effect` | `normal` | `required` | +| `workflow.get` | `GET` | `/api/v1/workflow/{workflow_id}` | `admin` | `system_admin` | `safe_read` | `private` | `none` | +| `workflow.list` | `GET` | `/api/v1/workflow/agent` | `bound_user` | `user` | `safe_read` | `normal` | `none` | +| `workflow.pause` | `POST` | `/api/v1/workflow/{workflow_id}/pause` | `admin` | `system_admin` | `reversible_write` | `normal` | `required` | +| `workflow.plugin.actions` | `GET` | `/api/v1/workflow/plugin/actions` | `admin` | `system_admin` | `safe_read` | `normal` | `none` | +| `workflow.reset` | `POST` | `/api/v1/workflow/{workflow_id}/reset` | `admin` | `system_admin` | `reversible_write` | `normal` | `required` | +| `workflow.run` | `POST` | `/api/v1/workflow/{workflow_id}/run` | `admin` | `system_admin` | `external_side_effect` | `normal` | `required` | +| `workflow.share` | `POST` | `/api/v1/workflow/share` | `admin` | `system_admin` | `external_side_effect` | `normal` | `required` | +| `workflow.share.delete` | `DELETE` | `/api/v1/workflow/share/{share_id}` | `admin` | `system_admin` | `external_side_effect` | `normal` | `required` | +| `workflow.shares` | `GET` | `/api/v1/workflow/shares` | `admin` | `system_admin` | `safe_read` | `normal` | `none` | +| `workflow.start` | `POST` | `/api/v1/workflow/{workflow_id}/start` | `admin` | `system_admin` | `reversible_write` | `normal` | `required` | +| `workflow.update` | `PUT` | `/api/v1/workflow/{workflow_id}` | `admin` | `system_admin` | `reversible_write` | `normal` | `required` | + ## Bounded Dynamic Routes | method | route template | operations | constraint | diff --git a/scripts/generate_agent_api_surface_audit.py b/scripts/generate_agent_api_surface_audit.py index 8861b3cb7..e9ace1c39 100644 --- a/scripts/generate_agent_api_surface_audit.py +++ b/scripts/generate_agent_api_surface_audit.py @@ -15,7 +15,7 @@ PROJECT_ROOT = Path(__file__).resolve().parents[1] if str(PROJECT_ROOT) not in sys.path: sys.path.insert(0, str(PROJECT_ROOT)) -from app.agent.policy.api import API_OPERATION_ROUTES # noqa: E402 +from app.agent.policy.api import API_OPERATION_ROUTES, API_OPERATION_SPECS # noqa: E402 from app.api.apiv1 import api_router # noqa: E402 JSON_OUTPUT = PROJECT_ROOT / "docs/refactor/agent-api-surface-audit.json" @@ -302,6 +302,19 @@ def generate_audit() -> dict[str, Any]: "dynamic_gateway_routes": dynamic_gateway_routes, "disposition_counts": dict(sorted(counts.items())), "operations": entries, + "gateway_identity_contracts": [ + { + "operation_id": spec.operation_id, + "method": API_OPERATION_ROUTES[spec.operation_id].method, + "path": API_OPERATION_ROUTES[spec.operation_id].path, + "identity_scope": spec.identity_scope.value, + "required_role": spec.required_role.value, + "effect": spec.effect.value, + "result_sensitivity": spec.result_sensitivity.value, + "confirmation": spec.confirmation.value, + } + for spec in sorted(API_OPERATION_SPECS, key=lambda item: item.operation_id) + ], } @@ -339,6 +352,26 @@ def render_markdown(audit: dict[str, Any]) -> str: } for disposition, count in audit["disposition_counts"].items(): lines.append(f"| `{disposition}` | {count} | {meanings[disposition]} |") + lines.extend([ + "", + "## Gateway Identity Audit", + "", + "All gateway operations are audited below. New operations default to bound_user; safe_read alone never grants public access.", + "public: audited external public metadata only; the host-authenticated executor cannot call other operations.", + "bound_user: an active account bound to the stable channel sender ID, or the authenticated Web user ID; endpoint ownership and management permissions still apply.", + "admin: administrator authority, preserving existing verified channel/API integration semantics; this never substitutes for ownership on bound_user operations.", + "Title recognition retains bound_user because its Context exposes locally configured applied identification rules. File recognition, source availability, rules, search results, media libraries, storage and public UI settings remain instance data.", + "Plugin ratings include this installation's own rating and require the same administrator role as their REST dependencies. Community share/popularity/statistics and global plugin installation statistics are public reads; follow lists and rating submissions are not.", + "", + "| operation | method | path | identity scope | role | effect | result sensitivity | confirmation |", + "| :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- |", + ]) + for item in audit["gateway_identity_contracts"]: + lines.append( + f"| `{item['operation_id']}` | `{item['method']}` | `{item['path']}` | " + f"`{item['identity_scope']}` | `{item['required_role']}` | `{item['effect']}` | " + f"`{item['result_sensitivity']}` | `{item['confirmation']}` |" + ) if audit["dynamic_gateway_routes"]: lines.extend( [ diff --git a/scripts/generate_agent_skill_docs.py b/scripts/generate_agent_skill_docs.py index ee19ff666..57ccdf960 100644 --- a/scripts/generate_agent_skill_docs.py +++ b/scripts/generate_agent_skill_docs.py @@ -332,6 +332,7 @@ def _render_api_docs() -> str: f"### `{operation_id}`", f"`{route.method} {route.path}`; policy effect: `{effect}`.", f"Purpose: {description.split(' Method:', 1)[0].strip()}", + f"Identity scope: `{specs[operation_id].identity_scope.value}`; required role: `{specs[operation_id].required_role.value}`.", ] ) collection_contract = branch.get("x-moviepilot-collection") diff --git a/skills/moviepilot-api/SKILL.md b/skills/moviepilot-api/SKILL.md index 031ea9bb4..be87d3388 100644 --- a/skills/moviepilot-api/SKILL.md +++ b/skills/moviepilot-api/SKILL.md @@ -1,6 +1,6 @@ --- name: moviepilot-api -version: 50 +version: 53 description: >- Use this skill for MoviePilot product operations such as media search, torrent search, downloads, subscriptions, library checks, sites, storage, workflows, @@ -209,6 +209,10 @@ frontmatter allowlist, and the matching gateway contract together. ## API Surface Scope +Operations declare an identity scope: `public` reads external public metadata without requiring a channel account binding; `bound_user` uses the active MoviePilot user bound to the stable channel sender ID (Web uses its authenticated user ID); `admin` requires administrator authority. Read-only does not mean public. Channel nicknames never establish identity. Personal records and subscription writes retain their existing ownership and permission checks. + +When identity resolution fails, relay the gateway's actual remediation message and sender ID. Channel users bind their own MoviePilot account via avatar > Personal Information > Account Binding, fill the named channel field, and save. Do not invent a Settings > Users binding page, substitute nicknames/group/bot IDs, diagnose an ambiguous binding failure as a network issue, or ask Web users to bind a notification channel. Public operations do not require binding. + This Skill is the complete callable MoviePilot business API surface for the Agent. Every operation in `allowed-api-operations` has one exact parameter contract in a category file and one matching MCP `tools/list` branch. There is diff --git a/skills/moviepilot-api/api/config.md b/skills/moviepilot-api/api/config.md index 67526602f..1504c3313 100644 --- a/skills/moviepilot-api/api/config.md +++ b/skills/moviepilot-api/api/config.md @@ -7,6 +7,7 @@ Configuration discovery, identifiers, user settings, and system setting updates. ### `config.identifiers.get` `GET /api/v1/system/identifiers`; policy effect: `safe_read`. Purpose: Read the complete custom media-recognition identifier list. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: none @@ -14,6 +15,7 @@ Purpose: Read the complete custom media-recognition identifier list. ### `config.identifiers.update` `POST /api/v1/system/identifiers`; policy effect: `reversible_write`. Purpose: Replace the complete custom media-recognition identifier list. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: `expected_identifiers` (array|null): Previously read complete ordered list. When supplied, reject the replacement if the stored list has changed.; `identifiers` (array): Complete ordered list of custom recognition identifier rules. @@ -21,6 +23,7 @@ Purpose: Replace the complete custom media-recognition identifier list. ### `config.public.get` `GET /api/v1/system/setting/public/{key}`; policy effect: `safe_read`. Purpose: Read one explicitly public system setting by exact key. +Identity scope: `bound_user`; required role: `user`. - `path_params`: `key*` (string): Optional exact plugin data key used to narrow the returned preview. - `query`: none - `body`: none @@ -28,6 +31,7 @@ Purpose: Read one explicitly public system setting by exact key. ### `config.system.describe` `GET /api/v1/system/settings/describe/{setting_key}`; policy effect: `safe_read`. Purpose: Read one exact system setting contract, current value, and revision. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `setting_key*` (string): Exact registered setting key returned by config.system.get discovery. - `query`: `include_value` (boolean; default `True`): Return the current value in addition to the complete contract.; `show_secrets` (boolean; default `False`): Return unredacted secret values only when explicitly authorized. - `body`: none @@ -35,6 +39,7 @@ Purpose: Read one exact system setting contract, current value, and revision. ### `config.system.get` `GET /api/v1/system/settings`; policy effect: `safe_read`. Purpose: Discover registered system settings or read one exact setting. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: `group` (string|null; default `all`): Discovery group used when setting_key is omitted. Use config.system.list for a complete, machine-readable group index; this compatibility endpoint accepts all registered catalog groups, including settings and systemconfig.; `include_values` (boolean|null): Return full values. Defaults to true for one exact key and false for discovery results.; `keyword` (string|null): Case-insensitive substring used to discover matching keys, groups, or labels.; `setting_key` (string|null): Exact setting key. Accepts Settings field names such as APP_DOMAIN or LLM_MODEL, SystemConfigKey values or enum names such as Downloaders or MediaServers, and aliases that resolve to one unique setting. Omit it to use list-style discovery.; `show_secrets` (boolean; default `False`): Return unredacted secret values. Defaults to false and remains confirmation-protected. - `body`: none @@ -42,6 +47,7 @@ Purpose: Discover registered system settings or read one exact setting. ### `config.system.list` `GET /api/v1/system/settings/catalog`; policy effect: `safe_read`. Purpose: List compact machine-readable contracts for registered system settings. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: `group` (string|null; default `all`): Setting contract group. Use all to list every registered setting.; `keyword` (string|null): Case-insensitive substring matched against key, group, label, or description.; `limit` (integer; default `100`; minimum `1`; maximum `500`): Maximum contracts to return.; `offset` (integer; default `0`; minimum `0`): Zero-based result offset.; `source` (string|null): Optional source filter: settings or systemconfig. - `body`: none @@ -49,6 +55,7 @@ Purpose: List compact machine-readable contracts for registered system settings. ### `config.system.update` `POST /api/v1/system/settings`; policy effect: `reversible_write`. Purpose: Update one exact registered system setting. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: `expected_revision` (string|null): Optional revision returned by config.system.describe or config.system.get for this exact setting. When supplied, the update is rejected if another writer changed the setting after that read. New Agent clients should always provide it.; `match_field` (string|null): Object field used to match a list item. Downloaders, MediaServers, Notifications, Directories, and Storages default to name; NotificationSwitchs defaults to type. Supply it for other object lists.; `match_value` (value): Value compared against match_field. If omitted, use value[match_field]; scalar lists use value directly.; `operation` (string(replace,merge_dict,upsert_list_item,remove_list_item); default `replace`): replace overwrites the complete value; merge_dict shallow-merges an object; upsert_list_item inserts or replaces one matched list item; remove_list_item removes one matched list item.; `remove_keys` (array): Object keys to remove after merge_dict applies the supplied value.; `setting_key*` (string): Exact setting key. Accepts a Settings field name, a SystemConfigKey value or enum name, or an alias that resolves to one unique setting. Call config.system.get with group or keyword first when the key is unknown.; `value` (value): New value or list item. For replace, send the complete value. For merge_dict, send the object fragment to merge. For upsert_list_item or remove_list_item, send one object or scalar item. @@ -56,6 +63,7 @@ Purpose: Update one exact registered system setting. ### `config.user.get` `GET /api/v1/system/global/user`; policy effect: `safe_read`. Purpose: Read current-user feature flags, runtime capabilities, and effective permissions. +Identity scope: `bound_user`; required role: `user`. - `path_params`: none - `query`: none - `body`: none diff --git a/skills/moviepilot-api/api/dashboard.md b/skills/moviepilot-api/api/dashboard.md index bf058f1f3..260581d04 100644 --- a/skills/moviepilot-api/api/dashboard.md +++ b/skills/moviepilot-api/api/dashboard.md @@ -7,6 +7,7 @@ Dashboard statistics and runtime overview projections. ### `dashboard.cpu` `GET /api/v1/dashboard/cpu`; policy effect: `safe_read`. Purpose: Read the current host CPU utilization percentage. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: none @@ -14,6 +15,7 @@ Purpose: Read the current host CPU utilization percentage. ### `dashboard.downloader` `GET /api/v1/dashboard/downloader`; policy effect: `safe_read`. Purpose: Read aggregate downloader task counts, speeds, and free-space information. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: `name` (string|null): Human-readable name of the site, storage item, subscription, or rule group. - `body`: none @@ -21,6 +23,7 @@ Purpose: Read aggregate downloader task counts, speeds, and free-space informati ### `dashboard.media.statistics` `GET /api/v1/dashboard/statistic`; policy effect: `safe_read`. Purpose: Read aggregate movie, TV, episode, and music library counts. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: `name` (string|null): Human-readable name of the site, storage item, subscription, or rule group. - `body`: none @@ -28,6 +31,7 @@ Purpose: Read aggregate movie, TV, episode, and music library counts. ### `dashboard.memory` `GET /api/v1/dashboard/memory`; policy effect: `safe_read`. Purpose: Read current MoviePilot process and host memory utilization. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: none @@ -35,6 +39,7 @@ Purpose: Read current MoviePilot process and host memory utilization. ### `dashboard.network` `GET /api/v1/dashboard/network`; policy effect: `safe_read`. Purpose: Read the current host network receive and transmit counters. +Identity scope: `admin`; required role: `system_admin`. - `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated. - `path_params`: none - `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result. @@ -43,6 +48,7 @@ Purpose: Read the current host network receive and transmit counters. ### `dashboard.processes` `GET /api/v1/dashboard/processes`; policy effect: `safe_read`. Purpose: List host processes visible to the MoviePilot runtime. +Identity scope: `admin`; required role: `system_admin`. - `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated. - `path_params`: none - `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result. @@ -51,6 +57,7 @@ Purpose: List host processes visible to the MoviePilot runtime. ### `dashboard.storage` `GET /api/v1/dashboard/storage`; policy effect: `safe_read`. Purpose: Read local filesystem capacity and free-space information. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: none @@ -58,6 +65,7 @@ Purpose: Read local filesystem capacity and free-space information. ### `dashboard.system` `GET /api/v1/dashboard/system`; policy effect: `safe_read`. Purpose: Read MoviePilot host, runtime, platform, and uptime summary information. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: none @@ -65,6 +73,7 @@ Purpose: Read MoviePilot host, runtime, platform, and uptime summary information ### `dashboard.transfer.statistics` `GET /api/v1/dashboard/transfer`; policy effect: `safe_read`. Purpose: Read aggregate file-transfer counts grouped by time period. +Identity scope: `admin`; required role: `system_admin`. - `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated. - `path_params`: none - `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `days` (integer|null; default `7`): Recommendation time window in days.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result. diff --git a/skills/moviepilot-api/api/database.md b/skills/moviepilot-api/api/database.md index d86108f90..e6edf9aa4 100644 --- a/skills/moviepilot-api/api/database.md +++ b/skills/moviepilot-api/api/database.md @@ -7,6 +7,7 @@ Administrator database backup creation, verification, listing, and deletion. ### `database.backups.create` `POST /api/v1/system/database/backups`; policy effect: `external_side_effect`. Purpose: Create, verify, and atomically publish a managed database backup. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: none @@ -14,6 +15,7 @@ Purpose: Create, verify, and atomically publish a managed database backup. ### `database.backups.delete` `DELETE /api/v1/system/database/backups/{name}`; policy effect: `destructive_write`. Purpose: Delete one exact managed database backup artifact. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `name*` (string): Human-readable name of the site, storage item, subscription, or rule group. - `query`: none - `body`: none @@ -21,6 +23,7 @@ Purpose: Delete one exact managed database backup artifact. ### `database.backups.list` `GET /api/v1/system/database/backups`; policy effect: `safe_read`. Purpose: List managed database backup artifacts without exposing host paths. +Identity scope: `admin`; required role: `system_admin`. - `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated. - `path_params`: none - `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result. @@ -29,6 +32,7 @@ Purpose: List managed database backup artifacts without exposing host paths. ### `database.backups.verify` `POST /api/v1/system/database/backups/{name}/verify`; policy effect: `safe_read`. Purpose: Verify the integrity of one exact managed database backup artifact. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `name*` (string): Human-readable name of the site, storage item, subscription, or rule group. - `query`: none - `body`: none diff --git a/skills/moviepilot-api/api/download.md b/skills/moviepilot-api/api/download.md index 6ff80d5ab..7f5f4b35a 100644 --- a/skills/moviepilot-api/api/download.md +++ b/skills/moviepilot-api/api/download.md @@ -7,6 +7,7 @@ Download submission, configured clients, paths, active tasks, and download histo ### `download.add` `POST /api/v1/download/add`; policy effect: `external_side_effect`. Purpose: Submit one torrent to MoviePilot's normal download workflow. +Identity scope: `bound_user`; required role: `user`. - `path_params`: none - `query`: none - `body`: `allow_unrecognized` (boolean; default `False`): Allow a download when MoviePilot cannot resolve a canonical media identity.; `downloader` (string|null): Configured downloader instance name.; `media_id` (string|null): Source-native media ID. Always pair it with the exact media_source returned by search.; `media_source` (MediaSource|null): Metadata source identifier. Preserve the exact value returned with media_id.; `music_type` (string(recording,album)|null): Music identity level: recording, album, or artist where supported.; `save_path` (string|null): Configured downloader-side save path, or the exact name (alias) of a configured download directory, for the download or subscription.; `torrent_in*` (TorrentInfo): Complete torrent candidate returned by search.results or search.torrents. @@ -14,6 +15,7 @@ Purpose: Submit one torrent to MoviePilot's normal download workflow. ### `download.artist_collection` `POST /api/v1/download/artist-collection`; policy effect: `external_side_effect`. Purpose: Submit one artist-wide music torrent to MoviePilot's collection download workflow. +Identity scope: `bound_user`; required role: `user`. - `path_params`: none - `query`: none - `body`: `artist_id*` (string; minimum length `1`): Source-native artist ID returned by music search or an album detail response.; `artist_name*` (string; minimum length `1`): Artist name preserved as the identity label for an artist-wide music collection.; `downloader` (string|null): Configured downloader instance name.; `media_source*` (MediaSource): Metadata source identifier. Preserve the exact value returned with media_id.; `save_path` (string|null): Configured downloader-side save path, or the exact name (alias) of a configured download directory, for the download or subscription.; `torrent_in*` (TorrentInfo): Complete torrent candidate returned by search.results or search.torrents. @@ -21,6 +23,7 @@ Purpose: Submit one artist-wide music torrent to MoviePilot's collection downloa ### `download.clients` `GET /api/v1/download/clients`; policy effect: `safe_read`. Purpose: List enabled downloader instance names and provider types without credentials. +Identity scope: `bound_user`; required role: `user`. - `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated. - `path_params`: none - `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result. @@ -29,6 +32,7 @@ Purpose: List enabled downloader instance names and provider types without crede ### `download.history.delete` `DELETE /api/v1/history/download`; policy effect: `destructive_write`. Purpose: Delete one MoviePilot download-history record. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: `channel` (string|null): Message channel that originally submitted the download.; `classification_policy_revision` (integer|null): Policy revision that produced the persisted classification snapshot.; `classification_rule_id` (string|null): Stable rule ID that selected the persisted classification category.; `classification_source` (string|null): Selection source recorded with the persisted classification snapshot.; `date` (string|null): Record creation or completion timestamp used by the history item.; `download_hash` (string|null): Provider-native torrent hash associated with the record.; `episode_group` (string|null): TMDB episode-group identifier used for alternate episode ordering.; `episodes` (string|null): Episode-number expression recorded in history, such as E01-E03.; `id*` (integer): Persistent database identifier of the supplied record.; `image` (string|null): Image URL stored with the history record.; `media_category` (string|null): MoviePilot library category assigned to the media.; `media_category_id` (string|null): Stable classification category ID; preserve it separately from the current category path snapshot.; `media_id` (string|null): Source-native media ID. Always pair it with the exact media_source returned by search.; `media_source` (MediaSource|null): Metadata source identifier. Preserve the exact value returned with media_id.; `music_type` (string|null): Music identity level: recording, album, or artist where supported.; `note` (JsonData-Input|null): Structured auxiliary metadata stored with the record.; `path` (string|null): Storage or history path represented by this record.; `poster` (string|null): Poster image URL stored with the media or subscription.; `seasons` (string|null): Season-number expression recorded in history.; `title` (string|null): Media, torrent, subscription, or history title used by the operation.; `torrent_description` (string|null): Torrent release description recorded in download history.; `torrent_name` (string|null): Torrent release name recorded in download history.; `torrent_site` (string|null): Source site name recorded in download history.; `type` (string|null): MoviePilot media or storage item type required by the selected operation.; `userid` (string|null): Message-channel user ID recorded with download history.; `username` (string|null): MoviePilot or site username required by the selected operation.; `year` (string|null): Release or premiere year used to disambiguate the media title. @@ -36,6 +40,7 @@ Purpose: Delete one MoviePilot download-history record. ### `download.history.list` `GET /api/v1/history/download`; policy effect: `safe_read`. Purpose: Page MoviePilot download-history records in reverse chronological order. +Identity scope: `bound_user`; required role: `user`. - `response`: `data` remains a list and the endpoint's documented pagination or limit defaults remain in effect. `collection.result_count` reports the returned items and `collection.total_count` reports the exact total. For a count-only request, use the smallest valid page and read that metadata instead of querying the database after item truncation. - `path_params`: none - `query`: `count` (integer|null; default `30`): Maximum number of records to return on the requested page.; `page` (integer|null; default `1`): One-based result page number. @@ -44,6 +49,7 @@ Purpose: Page MoviePilot download-history records in reverse chronological order ### `download.paths` `GET /api/v1/download/paths`; policy effect: `safe_read`. Purpose: List configured downloader save-path URIs that may be passed to download.add. Each item's non-empty name is also accepted as save_path. +Identity scope: `bound_user`; required role: `user`. - `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated. - `path_params`: none - `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result. @@ -52,6 +58,7 @@ Purpose: List configured downloader save-path URIs that may be passed to downloa ### `download.tasks.active` `GET /api/v1/download/`; policy effect: `safe_read`. Purpose: List currently downloading MoviePilot tasks with their canonical media context. +Identity scope: `bound_user`; required role: `user`. - `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated. - `path_params`: none - `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `name` (string|null): Human-readable name of the site, storage item, subscription, or rule group.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result. diff --git a/skills/moviepilot-api/api/filter.md b/skills/moviepilot-api/api/filter.md index 8e3b24b26..12ca56572 100644 --- a/skills/moviepilot-api/api/filter.md +++ b/skills/moviepilot-api/api/filter.md @@ -7,6 +7,7 @@ Built-in and custom filter rules, groups, and rule testing. ### `filter.builtin` `GET /api/v1/rule/builtin`; policy effect: `safe_read`. Purpose: List built-in torrent filter rules. +Identity scope: `bound_user`; required role: `user`. - `path_params`: none - `query`: `rule_ids` (array|null): Exact built-in rule IDs to return. Repeat rule_ids in the query string; omit it to list every built-in rule. - `body`: none @@ -14,6 +15,7 @@ Purpose: List built-in torrent filter rules. ### `filter.custom` `GET /api/v1/rule/custom`; policy effect: `safe_read`. Purpose: List user-defined torrent filter rules. +Identity scope: `bound_user`; required role: `user`. - `path_params`: none - `query`: `include_group_refs` (boolean; default `True`): Include custom rules referenced only through rule groups.; `rule_ids` (array|null): Exact custom rule IDs to return. Repeat rule_ids in the query string; omit it to list every custom rule. - `body`: none @@ -21,6 +23,7 @@ Purpose: List user-defined torrent filter rules. ### `filter.custom.add` `POST /api/v1/rule/custom`; policy effect: `reversible_write`. Purpose: Create one user-defined torrent filter rule. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: `exclude` (string|null): Regular expression or filter expression that rejects matching releases.; `include` (string|null): Regular expression or filter expression that a release must match.; `name*` (string): Human-readable name of the site, storage item, subscription, or rule group.; `publish_time` (string|null): Release-age filter expression for a custom filter rule.; `rule_id*` (string): Stable custom filter-rule ID.; `seeders` (string|null): Minimum seeder expression for a filter rule, or the torrent's seeder count.; `size_range` (string|null): Accepted torrent size range expression for a custom filter rule. @@ -28,6 +31,7 @@ Purpose: Create one user-defined torrent filter rule. ### `filter.custom.delete` `DELETE /api/v1/rule/custom/{rule_id}`; policy effect: `destructive_write`. Purpose: Delete one user-defined torrent filter rule. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `rule_id*` (string): Stable custom filter-rule ID. - `query`: none - `body`: none @@ -35,6 +39,7 @@ Purpose: Delete one user-defined torrent filter rule. ### `filter.custom.update` `PUT /api/v1/rule/custom/{rule_id}`; policy effect: `reversible_write`. Purpose: Update one user-defined torrent filter rule. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `rule_id*` (string): Stable custom filter-rule ID. - `query`: none - `body`: `exclude` (string|null): Regular expression or filter expression that rejects matching releases.; `include` (string|null): Regular expression or filter expression that a release must match.; `name` (string|null): Human-readable name of the site, storage item, subscription, or rule group.; `new_rule_id` (string|null): Replacement stable ID for the existing custom filter rule.; `publish_time` (string|null): Release-age filter expression for a custom filter rule.; `seeders` (string|null): Minimum seeder expression for a filter rule, or the torrent's seeder count.; `size_range` (string|null): Accepted torrent size range expression for a custom filter rule. @@ -42,6 +47,7 @@ Purpose: Update one user-defined torrent filter rule. ### `filter.group.add` `POST /api/v1/rule/groups`; policy effect: `reversible_write`. Purpose: Create one named filter-rule group. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: `category` (string|null): MoviePilot media category or filter-group category, depending on the operation.; `media_type` (string|null): MoviePilot media type used to filter recommendations or rule groups.; `name*` (string): Human-readable name of the site, storage item, subscription, or rule group.; `rule_string*` (string): Ordered filter-rule expression stored in the group. @@ -49,6 +55,7 @@ Purpose: Create one named filter-rule group. ### `filter.group.delete` `DELETE /api/v1/rule/groups/{name}`; policy effect: `destructive_write`. Purpose: Delete one named filter-rule group. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `name*` (string): Human-readable name of the site, storage item, subscription, or rule group. - `query`: none - `body`: none @@ -56,6 +63,7 @@ Purpose: Delete one named filter-rule group. ### `filter.group.update` `PUT /api/v1/rule/groups/{name}`; policy effect: `reversible_write`. Purpose: Update or rename one named filter-rule group. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `name*` (string): Human-readable name of the site, storage item, subscription, or rule group. - `query`: none - `body`: `category` (string|null): MoviePilot media category or filter-group category, depending on the operation.; `media_type` (string|null): MoviePilot media type used to filter recommendations or rule groups.; `new_name` (string|null): Replacement name for the existing filter-rule group.; `rule_string` (string|null): Ordered filter-rule expression stored in the group. @@ -63,6 +71,7 @@ Purpose: Update or rename one named filter-rule group. ### `filter.groups` `GET /api/v1/rule/groups`; policy effect: `safe_read`. Purpose: List named filter-rule groups. +Identity scope: `bound_user`; required role: `user`. - `path_params`: none - `query`: `group_names` (array|null): Exact rule-group names to return. Repeat group_names in the query string; omit it to list every group.; `include_usage` (boolean; default `True`): Include the subscriptions or defaults that reference each rule group. - `body`: none @@ -70,6 +79,7 @@ Purpose: List named filter-rule groups. ### `filter.test` `GET /api/v1/system/ruletest`; policy effect: `external_side_effect`. Purpose: Test one title and optional subtitle against an exact named filter-rule group. +Identity scope: `bound_user`; required role: `user`. - `path_params`: none - `query`: `rulegroup_name*` (string): Exact filter-rule group name returned by filter.groups.; `subtitle` (string|null): Optional subtitle text used together with title during media recognition.; `title*` (string): Media, torrent, subscription, or history title used by the operation. - `body`: none diff --git a/skills/moviepilot-api/api/library.md b/skills/moviepilot-api/api/library.md index 7586c0f39..4c25c1701 100644 --- a/skills/moviepilot-api/api/library.md +++ b/skills/moviepilot-api/api/library.md @@ -7,6 +7,7 @@ Library existence checks and latest-media inspection. ### `library.exists` `GET /api/v1/mediaserver/exists`; policy effect: `safe_read`. Purpose: Check configured media servers for one canonical media identity. +Identity scope: `bound_user`; required role: `user`. - `path_params`: none - `query`: `media_id` (string|null): Source-native media ID. Always pair it with the exact media_source returned by search.; `media_source` (MediaSource|null): Metadata source identifier. Preserve the exact value returned with media_id.; `mtype` (string|null): MoviePilot media type or subscription-history category required by the operation.; `season` (integer|null): Season number used by the media, search, subscription, or transfer operation.; `title` (string|null): Media, torrent, subscription, or history title used by the operation.; `year` (string|null): Release or premiere year used to disambiguate the media title. - `body`: none @@ -14,6 +15,7 @@ Purpose: Check configured media servers for one canonical media identity. ### `library.latest` `GET /api/v1/mediaserver/latest`; policy effect: `safe_read`. Purpose: List recently added items from one configured media-server instance for the current user. +Identity scope: `bound_user`; required role: `user`. - `response`: `data` remains a list and `collection.result_count` reports the returned items. `collection.total_count` is omitted because this endpoint or its upstream source does not expose a total. - `path_params`: none - `query`: `count` (integer|null; default `20`): Maximum number of records to return on the requested page.; `server*` (string): Exact configured media-server instance name returned by the media-server instance list. diff --git a/skills/moviepilot-api/api/media.md b/skills/moviepilot-api/api/media.md index 312ca6dd2..14f778ca7 100644 --- a/skills/moviepilot-api/api/media.md +++ b/skills/moviepilot-api/api/media.md @@ -37,6 +37,7 @@ other clients. ### `media.cache.clear` `DELETE /api/v1/tmdb/cache`; policy effect: `destructive_write`. Purpose: Clear the complete administrator-only TMDB recognition cache. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: none @@ -44,6 +45,7 @@ Purpose: Clear the complete administrator-only TMDB recognition cache. ### `media.cache.delete` `DELETE /api/v1/tmdb/cache/{cache_key}`; policy effect: `destructive_write`. Purpose: Delete one administrator-only TMDB recognition-cache entry by exact key. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `cache_key*` (string): Exact recognition-cache key returned by the corresponding recognition-cache get operation. - `query`: none - `body`: none @@ -51,6 +53,7 @@ Purpose: Delete one administrator-only TMDB recognition-cache entry by exact key ### `media.cache.get` `GET /api/v1/tmdb/cache`; policy effect: `safe_read`. Purpose: Inspect the administrator-only TMDB recognition cache and summary counts. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: none @@ -58,6 +61,7 @@ Purpose: Inspect the administrator-only TMDB recognition cache and summary count ### `media.classification.fields` `GET /api/v1/media/classification/fields`; policy effect: `safe_read`. Purpose: Read the media-classification field catalog and source capabilities. +Identity scope: `bound_user`; required role: `user`. - `path_params`: none - `query`: none - `body`: none @@ -65,6 +69,7 @@ Purpose: Read the media-classification field catalog and source capabilities. ### `media.classification.policy.get` `GET /api/v1/media/classification/policy`; policy effect: `safe_read`. Purpose: Read the active automatic media-classification policy. +Identity scope: `bound_user`; required role: `user`. - `path_params`: none - `query`: `template` (string|null): Pass default to read the built-in media-classification policy template; omit it to read the active policy. - `body`: none @@ -72,6 +77,7 @@ Purpose: Read the active automatic media-classification policy. ### `media.classification.policy.history` `GET /api/v1/media/classification/history`; policy effect: `safe_read`. Purpose: Read the bounded history of published media-classification policies. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: none @@ -79,6 +85,7 @@ Purpose: Read the bounded history of published media-classification policies. ### `media.classification.policy.impact` `POST /api/v1/media/classification/impact`; policy effect: `safe_read`. Purpose: Estimate how an unpublished media-classification policy changes bounded recent samples. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: `example_limit` (integer; default `20`; minimum `0.0`; maximum `50.0`): Maximum number of representative impact examples to return.; `expected_revision*` (integer; minimum `1.0`): Current revision from a preceding read, used to reject concurrent state changes.; `policy*` (ClassificationPolicy-Input): Complete or draft automatic media-classification policy.; `sample_limit` (integer; default `100`; minimum `1.0`; maximum `200.0`): Maximum number of recent records or samples to inspect for impact analysis.; `samples` (array): Explicit normalized fact samples used for classification impact analysis. @@ -86,6 +93,7 @@ Purpose: Estimate how an unpublished media-classification policy changes bounded ### `media.classification.policy.preview` `POST /api/v1/media/classification/preview`; policy effect: `safe_read`. Purpose: Preview automatic media classification for selected media or normalized facts. +Identity scope: `bound_user`; required role: `user`. - `path_params`: none - `query`: none - `body`: `input*` (ClassificationFactsPreviewInput|ClassificationMediaPreviewInput): Media or normalized-facts input used for a classification preview.; `policy` (ClassificationPolicy-Input|null): Complete or draft automatic media-classification policy. @@ -93,6 +101,7 @@ Purpose: Preview automatic media classification for selected media or normalized ### `media.classification.policy.rollback` `POST /api/v1/media/classification/rollback/{revision}`; policy effect: `reversible_write`. Purpose: Publish a selected historical media-classification policy as a new revision. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `revision*` (integer): Published classification policy revision or expected revision number. - `query`: none - `body`: `expected_revision*` (integer; minimum `1.0`): Current revision from a preceding read, used to reject concurrent state changes. @@ -100,6 +109,7 @@ Purpose: Publish a selected historical media-classification policy as a new revi ### `media.classification.policy.update` `PUT /api/v1/media/classification/policy`; policy effect: `reversible_write`. Purpose: Validate and publish a complete automatic media-classification policy. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: `expected_revision*` (integer; minimum `0.0`): Current revision from a preceding read, used to reject concurrent state changes.; `policy*` (ClassificationPolicy-Input): Complete or draft automatic media-classification policy. @@ -107,6 +117,7 @@ Purpose: Validate and publish a complete automatic media-classification policy. ### `media.classification.policy.validate` `POST /api/v1/media/classification/validate`; policy effect: `safe_read`. Purpose: Validate an unpublished automatic media-classification policy without saving it. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: `policy*` (ClassificationPolicy-Input): Complete or draft automatic media-classification policy. @@ -114,6 +125,7 @@ Purpose: Validate an unpublished automatic media-classification policy without s ### `media.detail` `GET /api/v1/media/{media_id}`; policy effect: `safe_read`. Purpose: Read canonical media details from one selected metadata source. +Identity scope: `public`; required role: `user`. - `path_params`: `media_id*` (string): Source-native media ID. Always pair it with the exact media_source returned by search. - `query`: `media_source*` (MediaSource): Metadata source identifier. Preserve the exact value returned with media_id.; `type_name*` (string): Explicit media type name used when source IDs alone are ambiguous. - `body`: none @@ -121,6 +133,7 @@ Purpose: Read canonical media details from one selected metadata source. ### `media.episode_group.seasons` `GET /api/v1/media/group/seasons/{episode_group}`; policy effect: `safe_read`. Purpose: List seasons defined by one exact TMDB episode-group identity. +Identity scope: `public`; required role: `user`. - `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated. - `path_params`: `episode_group*` (string): TMDB episode-group identifier used for alternate episode ordering. - `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result. @@ -129,6 +142,7 @@ Purpose: List seasons defined by one exact TMDB episode-group identity. ### `media.episode_groups` `GET /api/v1/media/groups/{tmdbid}`; policy effect: `safe_read`. Purpose: List alternate TMDB episode groups available for one TV media identity. +Identity scope: `public`; required role: `user`. - `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated. - `path_params`: `tmdbid*` (integer): TMDB media ID returned by media search or detail. - `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result. @@ -137,6 +151,7 @@ Purpose: List alternate TMDB episode groups available for one TV media identity. ### `media.episode_schedule` `GET /api/v1/tmdb/{tmdbid}/{season}`; policy effect: `safe_read`. Purpose: Read TMDB episode release information for one season. +Identity scope: `public`; required role: `user`. - `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated. - `path_params`: `season*` (integer): Season number used by the media, search, subscription, or transfer operation.; `tmdbid*` (integer): TMDB media ID returned by media search or detail. - `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `episode_group` (string|null): TMDB episode-group identifier used for alternate episode ordering.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result. @@ -145,6 +160,7 @@ Purpose: Read TMDB episode release information for one season. ### `media.person.credits` `GET /api/v1/{source}/person/credits/{person_id}`; policy effect: `safe_read`. Purpose: Read one person's credits from the selected metadata source. +Identity scope: `public`; required role: `user`. - `response`: `data` remains a list and `collection.result_count` reports the returned items. `collection.total_count` is omitted because this endpoint or its upstream source does not expose a total. - `path_params`: `person_id*` (integer): Source-native person ID.; `source*` (string(douban,tmdb,bangumi,anilist)): Metadata source that owns the person ID. - `query`: `count` (integer; default `20`; minimum `1`; maximum `50`): Page size used by Bangumi and AniList; other sources ignore it.; `page` (integer; default `1`; minimum `1`): One-based result page number. @@ -153,6 +169,7 @@ Purpose: Read one person's credits from the selected metadata source. ### `media.person.search` `GET /api/v1/media/search`; policy effect: `safe_read`. Purpose: Search people and music artists across selected metadata sources. +Identity scope: `public`; required role: `user`. - `response`: `data` remains a list and `collection.result_count` reports the returned items. `collection.total_count` is omitted because this endpoint or its upstream source does not expose a total. - `path_params`: none - `query`: `count` (integer; default `8`): Maximum number of records to return on the requested page.; `media_source` (array; default `[]`): Metadata source identifier. Preserve the exact value returned with media_id.; `music_type` (string(recording,album,artist)|null): Music identity level: recording, album, or artist where supported.; `page` (integer; default `1`): One-based result page number.; `title*` (string): Media, torrent, subscription, or history title used by the operation.; `type*` (string=person): Literal person, selecting person search instead of media search. @@ -161,6 +178,7 @@ Purpose: Search people and music artists across selected metadata sources. ### `media.recognize` `GET /api/v1/media/recognize`; policy effect: `safe_read`. Purpose: Recognize media identity from a title, subtitle, or custom rule context. +Identity scope: `bound_user`; required role: `user`. - `path_params`: none - `query`: `custom_words` (string|null): Custom recognition or rename words applied to this media workflow.; `media_source` (MediaSource|null): Metadata source identifier. Preserve the exact value returned with media_id.; `subtitle` (string|null): Optional subtitle text used together with title during media recognition.; `title*` (string): Media, torrent, subscription, or history title used by the operation. - `body`: none @@ -168,6 +186,7 @@ Purpose: Recognize media identity from a title, subtitle, or custom rule context ### `media.recognize_file` `GET /api/v1/media/recognize_file`; policy effect: `safe_read`. Purpose: Recognize canonical media identity from one exact filename and optional path context. +Identity scope: `bound_user`; required role: `user`. - `path_params`: none - `query`: `media_source` (MediaSource|null): Metadata source identifier. Preserve the exact value returned with media_id.; `path*` (string): Storage or history path represented by this record. - `body`: none @@ -175,6 +194,7 @@ Purpose: Recognize canonical media identity from one exact filename and optional ### `media.scrape` `POST /api/v1/media/scrape/{storage}`; policy effect: `external_side_effect`. Purpose: Generate or refresh metadata for one storage item. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `storage*` (string|null): Configured storage name or storage type used by the operation. - `query`: `episode_group` (string|null): TMDB episode-group identifier used for alternate episode ordering.; `media_id` (string|null): Source-native media ID. Always pair it with the exact media_source returned by search.; `media_source` (MediaSource|null): Metadata source identifier. Preserve the exact value returned with media_id.; `music_type` (string|null): Music identity level: recording, album, or artist where supported.; `type_name` (MediaType|null): Explicit media type name used when source IDs alone are ambiguous. - `body`: `basename` (string|null): Base filename without its parent path.; `children` (array|null): Child storage items nested below this item.; `drive_id` (string|null): Provider-native storage drive identifier.; `extension` (string|null): Filename extension, including or excluding the leading dot as returned by storage.; `fileid` (string|null): Provider-native storage item identifier.; `modify_time` (number|null): Storage item modification timestamp.; `name` (string|null): Human-readable name of the site, storage item, subscription, or rule group.; `parent_fileid` (string|null): Provider-native identifier of the parent storage directory.; `path` (string|null; default `/`): Storage or history path represented by this record.; `pickcode` (string|null): 115 storage pickcode associated with the item.; `size` (integer|null): File or torrent size in bytes.; `storage` (string|null; default `local`): Configured storage name or storage type used by the operation.; `thumbnail` (string|null): Thumbnail URL returned by the storage provider.; `type` (string|null): MoviePilot media or storage item type required by the selected operation.; `url` (string|null): Site, storage, or torrent URL represented by this field. @@ -182,6 +202,7 @@ Purpose: Generate or refresh metadata for one storage item. ### `media.search` `GET /api/v1/media/search`; policy effect: `safe_read`. Purpose: Search canonical media across selected metadata sources. +Identity scope: `public`; required role: `user`. - `response`: `data` remains a list and `collection.result_count` reports the returned items. `collection.total_count` is omitted because this endpoint or its upstream source does not expose a total. - `path_params`: none - `query`: `count` (integer; default `8`): Maximum number of records to return on the requested page.; `media_source` (array; default `[]`): Metadata source identifier. Preserve the exact value returned with media_id.; `music_type` (string(recording,album,artist)|null): Music identity level: recording, album, or artist where supported.; `page` (integer; default `1`): One-based result page number.; `title*` (string): Media, torrent, subscription, or history title used by the operation.; `type` (string|null; default `media`): MoviePilot media or storage item type required by the selected operation. @@ -190,6 +211,7 @@ Purpose: Search canonical media across selected metadata sources. ### `media.seasons` `GET /api/v1/media/seasons`; policy effect: `safe_read`. Purpose: List seasons for one exact media identity or a title-and-year fallback. +Identity scope: `public`; required role: `user`. - `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated. - `path_params`: none - `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `media_id` (string|null): Source-native media ID. Always pair it with the exact media_source returned by search.; `media_source` (MediaSource|null): Metadata source identifier. Preserve the exact value returned with media_id.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.; `season` (integer): Season number used by the media, search, subscription, or transfer operation.; `title` (string|null): Media, torrent, subscription, or history title used by the operation.; `year` (string): Release or premiere year used to disambiguate the media title. @@ -198,6 +220,7 @@ Purpose: List seasons for one exact media identity or a title-and-year fallback. ### `media.sources` `GET /api/v1/media/source`; policy effect: `safe_read`. Purpose: List metadata sources currently registered for MoviePilot media operations. +Identity scope: `bound_user`; required role: `user`. - `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated. - `path_params`: none - `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result. diff --git a/skills/moviepilot-api/api/music.md b/skills/moviepilot-api/api/music.md index 9f39565f3..96c0ad70e 100644 --- a/skills/moviepilot-api/api/music.md +++ b/skills/moviepilot-api/api/music.md @@ -205,6 +205,7 @@ online confirmation; album corrections must preserve the returned source-file sc ### `music.album.get` `GET /api/v1/music/album/{album_id}`; policy effect: `safe_read`. Purpose: Read one album's details, tracks, releases, and aligned artist names and IDs. +Identity scope: `public`; required role: `user`. - Album and track text follows `MUSIC_METADATA_TO_SIMPLIFIED`, matching recognition and final transfer naming. Original aliases, source data, lyrics, and IDs remain unchanged. Recording selection does not override local album-edition evidence, and multi-artist credits remain complete. - `path_params`: `album_id*` (string): Source-native album ID returned by music search, exploration, or artist-album browsing. - `query`: `media_source` (MediaSource): Metadata source identifier. Preserve the exact value returned with media_id.; `musicbrainz_release_id` (string|null): Optional exact MusicBrainz Release UUID belonging to the selected album Release Group; never use it as media_id. @@ -213,6 +214,7 @@ Purpose: Read one album's details, tracks, releases, and aligned artist names an ### `music.album.related` `GET /api/v1/music/album/{album_id}/related`; policy effect: `safe_read`. Purpose: Browse albums related to one source-native album identity. +Identity scope: `public`; required role: `user`. - `response`: `data` remains a list and `collection.result_count` reports the returned items. `collection.total_count` is omitted because this endpoint or its upstream source does not expose a total. - `path_params`: `album_id*` (string): Source-native album ID returned by music search, exploration, or artist-album browsing. - `query`: `count` (integer; default `24`; minimum `1`; maximum `100`): Maximum number of records to return on the requested page.; `media_source` (MediaSource): Metadata source identifier. Preserve the exact value returned with media_id. @@ -221,6 +223,7 @@ Purpose: Browse albums related to one source-native album identity. ### `music.artist.albums` `GET /api/v1/music/artist/{artist_id}/albums`; policy effect: `safe_read`. Purpose: Browse one artist's albums, singles, EPs, or another exact release-group type. +Identity scope: `public`; required role: `user`. - `response`: `data` remains a list and `collection.result_count` reports the returned items. `collection.total_count` is omitted because this endpoint or its upstream source does not expose a total. - `path_params`: `artist_id*` (string): Source-native artist ID returned by music search or an album detail response. - `query`: `album_type` (string|null): Music album or release-group type used by classification rules.; `count` (integer; default `30`; minimum `1`; maximum `100`): Maximum number of records to return on the requested page.; `media_source` (MediaSource): Metadata source identifier. Preserve the exact value returned with media_id.; `page` (integer; default `1`; minimum `1`): One-based result page number. @@ -229,6 +232,7 @@ Purpose: Browse one artist's albums, singles, EPs, or another exact release-grou ### `music.artist.get` `GET /api/v1/music/artist/{artist_id}`; policy effect: `safe_read`. Purpose: Read one artist's canonical details from the selected music metadata source. +Identity scope: `public`; required role: `user`. - `path_params`: `artist_id*` (string): Source-native artist ID returned by music search or an album detail response. - `query`: `media_source` (MediaSource): Metadata source identifier. Preserve the exact value returned with media_id. - `body`: none @@ -236,6 +240,7 @@ Purpose: Read one artist's canonical details from the selected music metadata so ### `music.artist.related` `GET /api/v1/music/artist/{artist_id}/related`; policy effect: `safe_read`. Purpose: Browse artists related to one source-native artist identity. +Identity scope: `public`; required role: `user`. - `response`: `data` remains a list and `collection.result_count` reports the returned items. `collection.total_count` is omitted because this endpoint or its upstream source does not expose a total. - `path_params`: `artist_id*` (string): Source-native artist ID returned by music search or an album detail response. - `query`: `count` (integer; default `24`; minimum `1`; maximum `100`): Maximum number of records to return on the requested page.; `media_source` (MediaSource): Metadata source identifier. Preserve the exact value returned with media_id. @@ -244,6 +249,7 @@ Purpose: Browse artists related to one source-native artist identity. ### `music.cache.clear` `DELETE /api/v1/music/cache`; policy effect: `destructive_write`. Purpose: Clear the complete administrator-only MusicBrainz recognition cache. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: none @@ -251,6 +257,7 @@ Purpose: Clear the complete administrator-only MusicBrainz recognition cache. ### `music.cache.delete` `DELETE /api/v1/music/cache/{cache_key}`; policy effect: `destructive_write`. Purpose: Delete one administrator-only MusicBrainz recognition-cache entry by exact key. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `cache_key*` (string): Exact recognition-cache key returned by the corresponding recognition-cache get operation. - `query`: none - `body`: none @@ -258,6 +265,7 @@ Purpose: Delete one administrator-only MusicBrainz recognition-cache entry by ex ### `music.cache.get` `GET /api/v1/music/cache`; policy effect: `safe_read`. Purpose: Inspect the administrator-only MusicBrainz recognition cache and summary counts. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: none @@ -265,6 +273,7 @@ Purpose: Inspect the administrator-only MusicBrainz recognition cache and summar ### `music.explore` `GET /api/v1/music/explore`; policy effect: `safe_read`. Purpose: Browse MusicBrainz charts or fresh releases, or Douban Music tag categories. +Identity scope: `public`; required role: `user`. - `response`: `data` remains a list and `collection.result_count` reports the returned items. `collection.total_count` is omitted because this endpoint or its upstream source does not expose a total. - `path_params`: none - `query`: `count` (integer; default `30`; minimum `1`; maximum `100`): Maximum number of records to return on the requested page.; `days` (integer; default `14`; minimum `1`; maximum `90`): Fresh-release lookback/lookahead window, from 1 through the endpoint maximum.; `douban_sort` (string; default `U`): Douban Music order: U comprehensive, S rating, R newest, or O hottest.; `entity` (string; default `recording`): Chart entity: recording for tracks or album for release groups. Fresh results are albums.; `future` (boolean; default `True`): Include releases after today in fresh mode.; `media_source` (MediaSource): Music exploration source. Use musicbrainz for chart/fresh modes or doubanmusic for tag browsing.; `min_listen_count` (integer; default `0`; minimum `0`): Minimum ListenBrainz listen count in chart mode.; `mode` (string; default `chart`): MusicBrainz mode: chart reads listening charts; fresh reads new album releases.; `page` (integer; default `1`; minimum `1`): One-based result page number.; `past` (boolean; default `True`): Include releases before today in fresh mode.; `range_name` (string; default `this_month`): ListenBrainz chart range: this_week, this_month, this_year, week, month, or year.; `sort` (string; default `release_date`): Fresh-release order accepted by the current ListenBrainz implementation.; `sort_by` (string; default `listen_count.desc`): ListenBrainz chart order: listen_count.desc or listen_count.asc.; `tags` (string; default ``): Comma-separated Douban Music tags used only when media_source is doubanmusic.; `with_cover` (boolean; default `False`): Keep only results with cover artwork when true. @@ -273,6 +282,7 @@ Purpose: Browse MusicBrainz charts or fresh releases, or Douban Music tag catego ### `music.recognize` `POST /api/v1/music/recognize`; policy effect: `safe_read`. Purpose: Resolve one recording or album from an exact music source and source-native ID. +Identity scope: `public`; required role: `user`. - `path_params`: none - `query`: none - `body`: `media_id*` (string): Source-native media ID. Always pair it with the exact media_source returned by search.; `media_source*` (MediaSource): Metadata source identifier. Preserve the exact value returned with media_id.; `music_type` (string(recording,album)|null): Music identity level: recording, album, or artist where supported. diff --git a/skills/moviepilot-api/api/plugin.md b/skills/moviepilot-api/api/plugin.md index 6fb3ef817..5bb76df2a 100644 --- a/skills/moviepilot-api/api/plugin.md +++ b/skills/moviepilot-api/api/plugin.md @@ -7,6 +7,7 @@ Installed and marketplace plugins, configuration, lifecycle, sources, folders, r ### `plugin.capabilities` `GET /api/v1/plugin/runtime/capabilities`; policy effect: `safe_read`. Purpose: Inspect the runtime capabilities exposed by installed plugins. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: `plugin_id` (string|null): Exact installed or marketplace plugin ID. - `body`: none @@ -14,6 +15,7 @@ Purpose: Inspect the runtime capabilities exposed by installed plugins. ### `plugin.clone` `POST /api/v1/plugin/clone/{plugin_id}`; policy effect: `external_side_effect`. Purpose: Create a configurable clone of one installed plugin; leave suffix empty to let the server allocate the next free instance ID, and reusing the suffix of a disabled clone re-enables that clone together with its stored configuration. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `plugin_id*` (string): Exact installed or marketplace plugin ID. - `query`: none - `body`: `description` (string; default ``): Human-readable media, torrent, or subscription description.; `icon` (string|null): Icon name or URL used by a workflow, network target, plugin, or category.; `name` (string; default ``): Human-readable name of the site, storage item, subscription, or rule group.; `restore_previous` (boolean; default `True`): When creating a plugin clone, reuse the stored configuration of a disabled clone that already holds the same suffix; false rebuilds that clone's configuration from the source plugin template.; `suffix` (string|null): File suffix or extension matched by an automatic category rule.; `version` (string|null): Plugin release or schema version selected by the operation. @@ -21,6 +23,7 @@ Purpose: Create a configurable clone of one installed plugin; leave suffix empty ### `plugin.clone.restorable` `GET /api/v1/plugin/clone/{plugin_id}/restorable`; policy effect: `safe_read`. Purpose: List one plugin's disabled clones whose configuration is still stored and can be brought back by creating a clone with the same suffix. +Identity scope: `admin`; required role: `system_admin`. - `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated. - `path_params`: `plugin_id*` (string): Exact installed or marketplace plugin ID. - `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result. @@ -29,6 +32,7 @@ Purpose: List one plugin's disabled clones whose configuration is still stored a ### `plugin.config.get` `GET /api/v1/plugin/form/{plugin_id}`; policy effect: `safe_read`. Purpose: Read one loaded plugin's configuration form and its defaults merged with saved values. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `plugin_id*` (string): Exact installed or marketplace plugin ID. - `query`: none - `body`: none @@ -36,6 +40,7 @@ Purpose: Read one loaded plugin's configuration form and its defaults merged wit ### `plugin.config.update` `PUT /api/v1/plugin/{plugin_id}`; policy effect: `reversible_write`. Purpose: Replace one installed plugin's complete configuration and apply it immediately. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `plugin_id*` (string): Exact installed or marketplace plugin ID. - `query`: none - `body*` (object): Complete plugin configuration object. First call plugin.config.get, copy its returned model, change only the intended keys, and submit the full resulting object. Omit a key only when it must be removed. A successful update reinitializes the plugin and refreshes commands, jobs, and routes. @@ -43,6 +48,7 @@ Purpose: Replace one installed plugin's complete configuration and apply it imme ### `plugin.data` `GET /api/v1/plugin/runtime/{plugin_id}/data`; policy effect: `safe_read`. Purpose: Read a bounded preview of one plugin's persisted data. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `plugin_id*` (string): Exact installed or marketplace plugin ID. - `query`: `key` (string|null): Optional exact plugin data key used to narrow the returned preview.; `max_chars` (integer|null): Maximum number of serialized plugin-data characters to return. - `body`: none @@ -50,6 +56,7 @@ Purpose: Read a bounded preview of one plugin's persisted data. ### `plugin.default_target.clear` `DELETE /api/v1/plugin/instances/{plugin_id}/{instance_id}/default_target`; policy effect: `reversible_write`. Purpose: Clear one plugin instance's default-call-target flag, only if it is the plugin's current default. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `instance_id*` (string): Exact plugin instance ID returned by plugin.loglevel.get.; `plugin_id*` (string): Exact installed or marketplace plugin ID. - `query`: none - `body`: none @@ -57,6 +64,7 @@ Purpose: Clear one plugin instance's default-call-target flag, only if it is the ### `plugin.default_target.set` `PUT /api/v1/plugin/instances/{plugin_id}/{instance_id}/default_target`; policy effect: `reversible_write`. Purpose: Set one plugin instance as the plugin's default call target, automatically clearing any previous default. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `instance_id*` (string): Exact plugin instance ID returned by plugin.loglevel.get.; `plugin_id*` (string): Exact installed or marketplace plugin ID. - `query`: none - `body`: none @@ -64,6 +72,7 @@ Purpose: Set one plugin instance as the plugin's default call target, automatica ### `plugin.folder.create` `POST /api/v1/plugin/folders/{folder_name}`; policy effect: `reversible_write`. Purpose: Create one named plugin folder. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `folder_name*` (string): Exact plugin folder name returned by plugin.folders.get. - `query`: none - `body`: none @@ -71,6 +80,7 @@ Purpose: Create one named plugin folder. ### `plugin.folder.delete` `DELETE /api/v1/plugin/folders/{folder_name}`; policy effect: `destructive_write`. Purpose: Delete one named plugin folder without uninstalling its plugins. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `folder_name*` (string): Exact plugin folder name returned by plugin.folders.get. - `query`: none - `body`: none @@ -78,6 +88,7 @@ Purpose: Delete one named plugin folder without uninstalling its plugins. ### `plugin.folder.plugin.assign` `PUT /api/v1/plugin/folders/{folder_name}/plugins/{plugin_id}`; policy effect: `reversible_write`. Purpose: Move one installed plugin into one named folder and remove its other folder assignments. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `folder_name*` (string): Exact plugin folder name returned by plugin.folders.get.; `plugin_id*` (string): Exact installed or marketplace plugin ID. - `query`: none - `body`: none @@ -85,6 +96,7 @@ Purpose: Move one installed plugin into one named folder and remove its other fo ### `plugin.folder.plugin.remove` `DELETE /api/v1/plugin/folders/{folder_name}/plugins/{plugin_id}`; policy effect: `reversible_write`. Purpose: Remove one installed plugin from one named folder without uninstalling it. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `folder_name*` (string): Exact plugin folder name returned by plugin.folders.get.; `plugin_id*` (string): Exact installed or marketplace plugin ID. - `query`: none - `body`: none @@ -92,6 +104,7 @@ Purpose: Remove one installed plugin from one named folder without uninstalling ### `plugin.folder.plugins.update` `PUT /api/v1/plugin/folders/{folder_name}/plugins`; policy effect: `reversible_write`. Purpose: Replace the ordered plugin IDs assigned to one named plugin folder. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `folder_name*` (string): Exact plugin folder name returned by plugin.folders.get. - `query`: none - `body*` (array|PluginFolderPluginsUpdateRequest): Request value for plugin.folder.plugins.update. Replace the ordered plugin IDs assigned to one named plugin folder. Use the exact type and fields below. @@ -99,6 +112,7 @@ Purpose: Replace the ordered plugin IDs assigned to one named plugin folder. ### `plugin.folder.update` `PATCH /api/v1/plugin/folders/{folder_name}`; policy effect: `reversible_write`. Purpose: Incrementally rename one plugin folder or update its presentation settings. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `folder_name*` (string): Exact plugin folder name returned by plugin.folders.get. - `query`: none - `body`: `background` (string|null): Optional folder background color or style.; `color` (string|null): Optional folder foreground color.; `gradient` (string|null): Optional folder gradient definition.; `icon` (string|null): Optional folder icon name.; `new_name` (string|null): Optional replacement folder name.; `showIcon` (boolean|null): Whether the frontend should display the folder icon. @@ -106,6 +120,7 @@ Purpose: Incrementally rename one plugin folder or update its presentation setti ### `plugin.folders.get` `GET /api/v1/plugin/folders`; policy effect: `safe_read`. Purpose: Read the complete administrator plugin-folder grouping configuration. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: none @@ -113,6 +128,7 @@ Purpose: Read the complete administrator plugin-folder grouping configuration. ### `plugin.folders.update` `POST /api/v1/plugin/folders`; policy effect: `reversible_write`. Purpose: Replace the complete administrator plugin-folder grouping configuration. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: `PluginFoldersData` with no direct fields @@ -120,6 +136,7 @@ Purpose: Replace the complete administrator plugin-folder grouping configuration ### `plugin.history` `GET /api/v1/plugin/history/{plugin_id}`; policy effect: `safe_read`. Purpose: Read marketplace update notes and history for one plugin. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `plugin_id*` (string): Exact installed or marketplace plugin ID. - `query`: `force` (boolean; default `True`): Force a marketplace refresh or plugin installation when true. - `body`: none @@ -127,6 +144,7 @@ Purpose: Read marketplace update notes and history for one plugin. ### `plugin.install` `GET /api/v1/plugin/install/{plugin_id}`; policy effect: `external_side_effect`. Purpose: Install or update one plugin from an approved source. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `plugin_id*` (string): Exact installed or marketplace plugin ID. - `query`: `force` (boolean|null; default `False`): Force a marketplace refresh or plugin installation when true.; `release_version` (string|null): Exact plugin release version to install when one is required.; `repo_url` (string|null; default ``): Approved plugin repository URL used to resolve the installation source. - `body`: none @@ -134,6 +152,7 @@ Purpose: Install or update one plugin from an approved source. ### `plugin.installed` `GET /api/v1/plugin/`; policy effect: `safe_read`. Purpose: List installed plugins and their runtime status. +Identity scope: `admin`; required role: `system_admin`. - `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated. - `path_params`: none - `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `force` (boolean; default `False`): Force a marketplace refresh or plugin installation when true.; `max_results` (integer|null): Optional upper bound on plugin catalog results, from 1 to 200; omit it for the complete catalog.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.; `query` (string|null): Optional case-insensitive keyword matched against plugin ID, name, description, and author.; `state*` (string=installed): Literal installed, selecting only installed plugin catalog entries. @@ -142,6 +161,7 @@ Purpose: List installed plugins and their runtime status. ### `plugin.instance.purge` `POST /api/v1/plugin/instance/{instance_id}/purge`; policy effect: `destructive_write`. Purpose: Permanently delete one plugin instance's selected data: saved configuration, plugin data rows, its own database and its data directory; a clone also loses its instance record. Nothing is deleted unless explicitly selected. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `instance_id*` (string): Exact plugin instance ID returned by plugin.loglevel.get. - `query`: none - `body`: `config` (boolean; default `False`): Purge scope flag: delete this plugin instance's saved configuration values.; `data_directory` (boolean; default `False`): Purge scope flag: delete this plugin instance's entire data directory; its own database is always destroyed first.; `own_database` (boolean; default `False`): Purge scope flag: destroy this plugin instance's own database.; `plugin_data` (boolean; default `False`): Purge scope flag: delete this plugin instance's rows in the shared plugin data table. @@ -149,6 +169,7 @@ Purpose: Permanently delete one plugin instance's selected data: saved configura ### `plugin.instance.set_enabled` `POST /api/v1/plugin/instance/{instance_id}/enabled`; policy effect: `reversible_write`. Purpose: Enable or disable one plugin instance, host or clone; disabling only stops it running and keeps its configuration for a later re-enable. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `instance_id*` (string): Exact plugin instance ID returned by plugin.loglevel.get. - `query`: none - `body`: `enabled*` (boolean): Whether this category or classification rule participates in evaluation. @@ -156,6 +177,7 @@ Purpose: Enable or disable one plugin instance, host or clone; disabling only st ### `plugin.loglevel.clear` `DELETE /api/v1/plugin/loglevel/{plugin_id}/{instance_id}`; policy effect: `reversible_write`. Purpose: Clear one plugin instance's log-level override so it immediately follows the global log level again. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `instance_id*` (string): Exact plugin instance ID returned by plugin.loglevel.get.; `plugin_id*` (string): Exact installed or marketplace plugin ID. - `query`: none - `body`: none @@ -163,6 +185,7 @@ Purpose: Clear one plugin instance's log-level override so it immediately follow ### `plugin.loglevel.get` `GET /api/v1/plugin/loglevel/{plugin_id}`; policy effect: `safe_read`. Purpose: List one plugin's instances, including the plugin itself, with each instance's configured and effective log level. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `plugin_id*` (string): Exact installed or marketplace plugin ID. - `query`: none - `body`: none @@ -170,6 +193,7 @@ Purpose: List one plugin's instances, including the plugin itself, with each ins ### `plugin.loglevel.set` `PUT /api/v1/plugin/loglevel/{plugin_id}/{instance_id}`; policy effect: `reversible_write`. Purpose: Set one plugin instance's log-level override, taking effect immediately without following the global log level. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `instance_id*` (string): Exact plugin instance ID returned by plugin.loglevel.get.; `plugin_id*` (string): Exact installed or marketplace plugin ID. - `query`: none - `body`: `expires_at` (string|null): Expiry timestamp for a plugin instance's log-level override; null means it never expires. A timestamp without a timezone is read as UTC.; `level*` (string): Target log level, one of DEBUG, INFO, WARNING, ERROR, or CRITICAL. @@ -177,6 +201,7 @@ Purpose: Set one plugin instance's log-level override, taking effect immediately ### `plugin.market` `GET /api/v1/plugin/`; policy effect: `safe_read`. Purpose: List plugins available from configured marketplaces. +Identity scope: `admin`; required role: `system_admin`. - `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated. - `path_params`: none - `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `force` (boolean; default `False`): Force a marketplace refresh or plugin installation when true.; `max_results` (integer|null): Optional upper bound on plugin catalog results, from 1 to 200; omit it for the complete catalog.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.; `query` (string|null): Optional case-insensitive keyword matched against plugin ID, name, description, and author.; `state*` (string=market): Literal market, selecting only market plugin catalog entries. @@ -185,6 +210,7 @@ Purpose: List plugins available from configured marketplaces. ### `plugin.market.sync_wiki` `POST /api/v1/system/setting/PLUGIN_MARKET/sync-wiki`; policy effect: `external_side_effect`. Purpose: Refresh the configured plugin marketplace repositories from the MoviePilot Wiki. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body` (PluginMarketSyncRequest|null): Request value for plugin.market.sync_wiki. Refresh the configured plugin marketplace repositories from the MoviePilot Wiki. Use the exact type and fields below. @@ -192,6 +218,7 @@ Purpose: Refresh the configured plugin marketplace repositories from the MoviePi ### `plugin.rating` `GET /api/v1/plugin/rating/{plugin_id}`; policy effect: `safe_read`. Purpose: Read the current aggregate rating for one plugin. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `plugin_id*` (string): Exact installed or marketplace plugin ID. - `query`: none - `body`: none @@ -199,6 +226,7 @@ Purpose: Read the current aggregate rating for one plugin. ### `plugin.rating.submit` `POST /api/v1/plugin/rating/{plugin_id}`; policy effect: `external_side_effect`. Purpose: Submit or replace the current user's rating for one plugin. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `plugin_id*` (string): Exact installed or marketplace plugin ID. - `query`: none - `body`: `rating*` (number; minimum `0.1`; maximum `5.0`): Numeric plugin rating accepted by the endpoint's declared bounds. @@ -206,6 +234,7 @@ Purpose: Submit or replace the current user's rating for one plugin. ### `plugin.ratings` `GET /api/v1/plugin/rating`; policy effect: `safe_read`. Purpose: Read aggregate ratings for a requested plugin set. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: `plugin_ids` (string|null): Exact plugin IDs whose aggregate ratings should be returned. - `body`: none @@ -213,6 +242,7 @@ Purpose: Read aggregate ratings for a requested plugin set. ### `plugin.releases` `GET /api/v1/plugin/releases/{plugin_id}`; policy effect: `safe_read`. Purpose: List available release versions for one plugin source. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `plugin_id*` (string): Exact installed or marketplace plugin ID. - `query`: `force` (boolean; default `False`): Force a marketplace refresh or plugin installation when true.; `repo_url` (string|null; default ``): Approved plugin repository URL used to resolve the installation source. - `body`: none @@ -220,6 +250,7 @@ Purpose: List available release versions for one plugin source. ### `plugin.reload` `POST /api/v1/plugin/reload/{plugin_id}`; policy effect: `external_side_effect`. Purpose: Reload one installed plugin into the running process. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `plugin_id*` (string): Exact installed or marketplace plugin ID. - `query`: none - `body`: none @@ -227,6 +258,7 @@ Purpose: Reload one installed plugin into the running process. ### `plugin.reset` `GET /api/v1/plugin/reset/{plugin_id}`; policy effect: `destructive_write`. Purpose: Delete one plugin's saved configuration and data, then restore its default runtime state. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `plugin_id*` (string): Exact installed or marketplace plugin ID. - `query`: none - `body`: none @@ -234,6 +266,7 @@ Purpose: Delete one plugin's saved configuration and data, then restore its defa ### `plugin.runtime.status` `GET /api/v1/plugin/runtime`; policy effect: `safe_read`. Purpose: Read plugin runtime convergence, loading, and failure state. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: none @@ -241,6 +274,7 @@ Purpose: Read plugin runtime convergence, loading, and failure state. ### `plugin.source.change` `POST /api/v1/plugin/source/{plugin_id}`; policy effect: `external_side_effect`. Purpose: Switch an installed plugin to one explicitly selected online source revision. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `plugin_id*` (string): Exact installed or marketplace plugin ID. - `query`: none - `body`: `expected_revision*` (integer; minimum `1.0`): Current revision from a preceding read, used to reject concurrent state changes.; `release_version` (string|null): Exact plugin release version to install when one is required.; `repo_url*` (string; minimum length `1`): Approved plugin repository URL used to resolve the installation source. @@ -248,6 +282,7 @@ Purpose: Switch an installed plugin to one explicitly selected online source rev ### `plugin.source.install` `POST /api/v1/plugin/source/{plugin_id}/install`; policy effect: `external_side_effect`. Purpose: Install an unbound plugin from one explicitly selected online source. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `plugin_id*` (string): Exact installed or marketplace plugin ID. - `query`: none - `body`: `force` (boolean; default `False`): Force a marketplace refresh or plugin installation when true.; `release_version` (string|null): Exact plugin release version to install when one is required.; `repo_url*` (string; minimum length `1`): Approved plugin repository URL used to resolve the installation source. @@ -255,6 +290,7 @@ Purpose: Install an unbound plugin from one explicitly selected online source. ### `plugin.source.options` `GET /api/v1/plugin/source/{plugin_id}`; policy effect: `safe_read`. Purpose: Inspect source candidates and the current immutable source identity before installation or source change. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `plugin_id*` (string): Exact installed or marketplace plugin ID. - `query`: none - `body`: none @@ -262,6 +298,7 @@ Purpose: Inspect source candidates and the current immutable source identity bef ### `plugin.statistics` `GET /api/v1/plugin/statistic`; policy effect: `safe_read`. Purpose: Read public installation statistics for plugins. +Identity scope: `public`; required role: `user`. - `path_params`: none - `query`: none - `body`: none @@ -269,6 +306,7 @@ Purpose: Read public installation statistics for plugins. ### `plugin.uninstall` `DELETE /api/v1/plugin/{plugin_id}`; policy effect: `destructive_write`. Purpose: Uninstall one plugin and remove it from the installed set. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `plugin_id*` (string): Exact installed or marketplace plugin ID. - `query`: none - `body`: none diff --git a/skills/moviepilot-api/api/recommendation.md b/skills/moviepilot-api/api/recommendation.md index bf5fb3e01..1112961eb 100644 --- a/skills/moviepilot-api/api/recommendation.md +++ b/skills/moviepilot-api/api/recommendation.md @@ -6,7 +6,8 @@ Recommendation listing. ### `recommendation.list` `GET /api/v1/recommend/agent`; policy effect: `safe_read`. -Purpose: Read personalized media or music recommendations. +Purpose: Read public media or music charts and recommendations. +Identity scope: `public`; required role: `user`. - `response`: `data` remains a list and `collection.result_count` reports the returned items. `collection.total_count` is omitted because this endpoint or its upstream source does not expose a total. - `path_params`: none - `query`: `days` (integer; default `14`): Recommendation time window in days.; `fresh_sort` (string; default `release_date`): Freshness ordering used by the recommendation source.; `future` (boolean; default `True`): Include future recommendation periods when supported.; `media_type` (string; default `all`): MoviePilot media type used to filter recommendations or rule groups.; `min_listen_count` (integer; default `0`): Minimum listen count required for a music recommendation.; `music_type` (string|null): Music identity level: recording, album, or artist where supported.; `page` (integer; default `1`): One-based result page number.; `past` (boolean; default `True`): Include past recommendation periods when supported.; `range_name` (string; default `this_month`): Named recommendation time range.; `sort_by` (string; default `listen_count.desc`): Recommendation field used for ordering results.; `source` (string; default `tmdb_trending`): Exact metadata or recommendation source selected by the operation.; `with_cover` (boolean; default `False`): Require recommendation results to include cover artwork. diff --git a/skills/moviepilot-api/api/scheduler.md b/skills/moviepilot-api/api/scheduler.md index b59e95f5f..92ceb1e2a 100644 --- a/skills/moviepilot-api/api/scheduler.md +++ b/skills/moviepilot-api/api/scheduler.md @@ -7,6 +7,7 @@ Scheduler listing, progress inspection, and confirmed task execution. ### `scheduler.list` `GET /api/v1/dashboard/schedule`; policy effect: `safe_read`. Purpose: List registered scheduler jobs and their current state. +Identity scope: `admin`; required role: `system_admin`. - `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated. - `path_params`: none - `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result. @@ -15,6 +16,7 @@ Purpose: List registered scheduler jobs and their current state. ### `scheduler.progress` `GET /api/v1/dashboard/schedule/{job_id}/progress`; policy effect: `safe_read`. Purpose: Read current progress for one exact scheduler job. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `job_id*` (string): Exact scheduler job ID returned by scheduler.list. - `query`: none - `body`: none @@ -22,6 +24,7 @@ Purpose: Read current progress for one exact scheduler job. ### `scheduler.run` `GET /api/v1/system/runscheduler`; policy effect: `external_side_effect`. Purpose: Run one registered scheduler job immediately. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: `jobid*` (string): Exact scheduler job ID returned by scheduler.list. - `body`: none diff --git a/skills/moviepilot-api/api/search.md b/skills/moviepilot-api/api/search.md index 011066f41..f9a297d27 100644 --- a/skills/moviepilot-api/api/search.md +++ b/skills/moviepilot-api/api/search.md @@ -7,6 +7,7 @@ Title and torrent search result discovery and recommendation search. ### `search.recommend` `POST /api/v1/search/recommend`; policy effect: `external_side_effect`. Purpose: Use the configured recommendation model to rank or recommend torrent search results. +Identity scope: `bound_user`; required role: `user`. - `path_params`: none - `query`: none - `body`: `check_only` (boolean; default `False`): Validate or preview the recommendation without applying search-result filtering.; `filtered_indices` (array|null): Zero-based search-result indices selected by the recommendation model.; `force` (boolean; default `False`): Force a marketplace refresh or plugin installation when true. @@ -14,6 +15,7 @@ Purpose: Use the configured recommendation model to rank or recommend torrent se ### `search.results` `GET /api/v1/search/last/context`; policy effect: `safe_read`. Purpose: Read the most recent torrent-search context and result set. +Identity scope: `bound_user`; required role: `user`. - `path_params`: none - `query`: none - `body`: none @@ -21,6 +23,7 @@ Purpose: Read the most recent torrent-search context and result set. ### `search.title` `GET /api/v1/search/title`; policy effect: `external_side_effect`. Purpose: Search torrent sites directly from a free-form title and optional media filters. +Identity scope: `bound_user`; required role: `user`. - `response`: `data` remains a list and `collection.result_count` reports the returned items. `collection.total_count` is omitted because this endpoint or its upstream source does not expose a total. - `path_params`: none - `query`: `keyword` (string|null): Case-insensitive substring used to discover settings or filter storage entries.; `mtype` (string|null): MoviePilot media type or subscription-history category required by the operation.; `page` (integer|null; default `0`): One-based result page number.; `sites` (string|null): Exact site IDs included in the search or subscription scope. @@ -29,6 +32,7 @@ Purpose: Search torrent sites directly from a free-form title and optional media ### `search.torrents` `GET /api/v1/search/media/{media_id}`; policy effect: `safe_read`. Purpose: Search torrent sites for one canonical media identity. +Identity scope: `bound_user`; required role: `user`. - `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated. - `execution`: Searches may run multiple title/alias rounds across configured sites. The inner Agent API request may wait up to 290 seconds and the Agent tool call is capped at 300 seconds, leaving 10 seconds for timeout handling and returning the result; the V3 MCP wrapper uses its existing configured timeout (600 seconds by default). `page` / `count` paginate the completed result and do not shorten site-search execution; do not issue a duplicate call while one is pending. - `path_params`: `media_id*` (string): Source-native media ID. Always pair it with the exact media_source returned by search. diff --git a/skills/moviepilot-api/api/site.md b/skills/moviepilot-api/api/site.md index 6ac191e92..959855021 100644 --- a/skills/moviepilot-api/api/site.md +++ b/skills/moviepilot-api/api/site.md @@ -7,6 +7,7 @@ Site discovery, authentication, cookies, user data, resources, RSS, priorities, ### `site.add` `POST /api/v1/site/`; policy effect: `reversible_write`. Purpose: Create one configured site with its complete authentication and search settings. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: `apikey` (string|null): Site API key used by sites that support API-key authentication.; `cookie` (string|null): Site authentication cookie. Treat this value as a secret.; `domain` (string|null): Site hostname or domain used for matching and requests.; `downloader` (string|null): Configured downloader instance name.; `filter` (string|null): Named filter rule or rule expression applied to this site or subscription.; `id` (integer|null): Persistent database identifier of the supplied record.; `is_active` (boolean|null; default `True`): Whether the configured site is enabled.; `limit_count` (integer|null): Maximum number of site requests allowed in one rate-limit interval.; `limit_interval` (integer|null): Number of requests in the site's rate-limit window.; `limit_seconds` (integer|null): Site rate-limit window length in seconds.; `name` (string|null): Human-readable name of the site, storage item, subscription, or rule group.; `note` (JsonData-Input|null): Structured auxiliary metadata stored with the record.; `pri` (integer|null; default `0`): Site search priority; lower or higher ordering follows the existing site API convention.; `proxy` (integer|null; default `0`): Whether the site uses MoviePilot's configured proxy.; `public` (integer|null; default `0`): Whether the site is treated as a public indexer.; `render` (integer|null; default `0`): Whether site requests require browser rendering.; `rss` (string|null): Site RSS feed URL.; `timeout` (integer|null; default `15`): Per-request site timeout in seconds.; `token` (string|null): Site authentication token. Treat this value as a secret.; `ua` (string|null): Site User-Agent string used for authenticated requests.; `url` (string|null): Site, storage, or torrent URL represented by this field. @@ -14,6 +15,7 @@ Purpose: Create one configured site with its complete authentication and search ### `site.auth.options` `GET /api/v1/site/auth`; policy effect: `safe_read`. Purpose: List site-account authentication providers and their required input definitions. +Identity scope: `bound_user`; required role: `user`. - `path_params`: none - `query`: none - `body`: none @@ -21,6 +23,7 @@ Purpose: List site-account authentication providers and their required input def ### `site.authenticate` `POST /api/v1/site/auth`; policy effect: `external_side_effect`. Purpose: Authenticate a supported site account and persist the resulting site authorization state. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: `params` (object|null): Provider-defined JSON parameters for the selected authentication or storage action.; `site` (string|null): Source site identifier associated with the torrent result. @@ -28,6 +31,7 @@ Purpose: Authenticate a supported site account and persist the resulting site au ### `site.category` `GET /api/v1/site/category/{site_id}`; policy effect: `safe_read`. Purpose: List torrent categories supported by one configured site. +Identity scope: `bound_user`; required role: `user`. - `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated. - `path_params`: `site_id*` (integer): Persistent site ID returned by site.list. - `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result. @@ -36,6 +40,7 @@ Purpose: List torrent categories supported by one configured site. ### `site.cookie.set` `POST /api/v1/site/cookie/{site_id}/set`; policy effect: `reversible_write`. Purpose: Persist a browser-obtained authentication cookie and optional User-Agent for one site without replacing other settings. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `site_id*` (integer): Persistent site ID returned by site.list. - `query`: none - `body`: `cookie*` (string): Site authentication Cookie header obtained from a trusted browser session.; `ua` (string|null): User-Agent associated with the authenticated browser session. @@ -43,6 +48,7 @@ Purpose: Persist a browser-obtained authentication cookie and optional User-Agen ### `site.cookie.update` `POST /api/v1/site/cookie/{site_id}`; policy effect: `reversible_write`. Purpose: Log in to one site and refresh its stored authentication cookie. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `site_id*` (integer): Persistent site ID returned by site.list. - `query`: none - `body`: `code` (string|null): Two-factor verification code or site-specific authentication secret.; `password*` (string): Site login password. Treat this value as a secret.; `username*` (string): MoviePilot or site username required by the selected operation. @@ -50,6 +56,7 @@ Purpose: Log in to one site and refresh its stored authentication cookie. ### `site.cookiecloud.sync` `POST /api/v1/site/cookiecloud`; policy effect: `external_side_effect`. Purpose: Start a CookieCloud synchronization of configured sites. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: none @@ -57,6 +64,7 @@ Purpose: Start a CookieCloud synchronization of configured sites. ### `site.delete` `DELETE /api/v1/site/{site_id}`; policy effect: `destructive_write`. Purpose: Delete one configured site by persistent site ID. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `site_id*` (integer): Persistent site ID returned by site.list. - `query`: none - `body`: none @@ -64,6 +72,7 @@ Purpose: Delete one configured site by persistent site ID. ### `site.list` `GET /api/v1/site/agent`; policy effect: `safe_read`. Purpose: List configured sites with status/name filters; authentication fields are returned only to a superuser. +Identity scope: `bound_user`; required role: `user`. - `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated. - `path_params`: none - `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `name` (string|null): Human-readable name of the site, storage item, subscription, or rule group.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.; `status` (string(active,inactive,all); default `all`): Transfer success status used to filter history or describe a record. @@ -72,6 +81,7 @@ Purpose: List configured sites with status/name filters; authentication fields a ### `site.mapping` `GET /api/v1/site/mapping`; policy effect: `safe_read`. Purpose: Read the configured site-domain to site-name mapping. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: none @@ -79,6 +89,7 @@ Purpose: Read the configured site-domain to site-name mapping. ### `site.priorities.update` `POST /api/v1/site/priorities`; policy effect: `reversible_write`. Purpose: Replace priorities for the supplied configured site IDs. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body*` (array): Request value for site.priorities.update. Replace priorities for the supplied configured site IDs. Use the exact type and fields below. @@ -86,6 +97,7 @@ Purpose: Replace priorities for the supplied configured site IDs. ### `site.reset` `POST /api/v1/site/reset`; policy effect: `destructive_write`. Purpose: Delete all configured sites and start a fresh CookieCloud synchronization. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: none @@ -93,6 +105,7 @@ Purpose: Delete all configured sites and start a fresh CookieCloud synchronizati ### `site.resource` `GET /api/v1/site/resource/{site_id}`; policy effect: `external_side_effect`. Purpose: Browse torrent resources from one configured site with category and keyword filters. +Identity scope: `admin`; required role: `system_admin`. - `response`: `data` remains a list and `collection.result_count` reports the returned items. `collection.total_count` is omitted because this endpoint or its upstream source does not expose a total. - `path_params`: `site_id*` (integer): Persistent site ID returned by site.list. - `query`: `cat` (string|null): Exact site category identifier returned by site.category.; `keyword` (string|null): Case-insensitive substring used to discover settings or filter storage entries.; `mtype` (string|null): MoviePilot media type or subscription-history category required by the operation.; `page` (integer|null; default `0`): One-based result page number. @@ -101,6 +114,7 @@ Purpose: Browse torrent resources from one configured site with category and key ### `site.rss` `GET /api/v1/site/rss`; policy effect: `safe_read`. Purpose: List configured sites selected for RSS subscription processing. +Identity scope: `admin`; required role: `system_admin`. - `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated. - `path_params`: none - `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result. @@ -109,6 +123,7 @@ Purpose: List configured sites selected for RSS subscription processing. ### `site.searchable` `GET /api/v1/site/media/{media_type}`; policy effect: `safe_read`. Purpose: List active configured sites supporting one exact media type. +Identity scope: `admin`; required role: `system_admin`. - `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated. - `path_params`: `media_type*` (string): MoviePilot media type used to filter recommendations or rule groups. - `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result. @@ -117,6 +132,7 @@ Purpose: List active configured sites supporting one exact media type. ### `site.statistic` `GET /api/v1/site/statistic/{site_url}`; policy effect: `safe_read`. Purpose: Read account and traffic statistics for one exact configured site domain. +Identity scope: `bound_user`; required role: `user`. - `path_params`: `site_url*` (string): Configured site URL or hostname used to select one site's statistics. - `query`: none - `body`: none @@ -124,6 +140,7 @@ Purpose: Read account and traffic statistics for one exact configured site domai ### `site.statistics` `GET /api/v1/site/statistic`; policy effect: `safe_read`. Purpose: Read the latest account and traffic statistics for all configured sites. +Identity scope: `bound_user`; required role: `user`. - `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated. - `path_params`: none - `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result. @@ -132,6 +149,7 @@ Purpose: Read the latest account and traffic statistics for all configured sites ### `site.supporting` `GET /api/v1/site/supporting`; policy effect: `safe_read`. Purpose: List indexer definitions supported by the installed MoviePilot resources. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: none @@ -139,6 +157,7 @@ Purpose: List indexer definitions supported by the installed MoviePilot resource ### `site.test` `GET /api/v1/site/test/{site_id}`; policy effect: `safe_read`. Purpose: Test connectivity and authentication for one configured site. +Identity scope: `bound_user`; required role: `user`. - `path_params`: `site_id*` (integer): Persistent site ID returned by site.list. - `query`: none - `body`: none @@ -146,6 +165,7 @@ Purpose: Test connectivity and authentication for one configured site. ### `site.update` `PUT /api/v1/site/`; policy effect: `reversible_write`. Purpose: Update one configured site's complete settings. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: `apikey` (string|null): Site API key used by sites that support API-key authentication.; `cookie` (string|null): Site authentication cookie. Treat this value as a secret.; `domain` (string|null): Site hostname or domain used for matching and requests.; `downloader` (string|null): Configured downloader instance name.; `filter` (string|null): Named filter rule or rule expression applied to this site or subscription.; `id` (integer|null): Persistent database identifier of the supplied record.; `is_active` (boolean|null; default `True`): Whether the configured site is enabled.; `limit_count` (integer|null): Maximum number of site requests allowed in one rate-limit interval.; `limit_interval` (integer|null): Number of requests in the site's rate-limit window.; `limit_seconds` (integer|null): Site rate-limit window length in seconds.; `name` (string|null): Human-readable name of the site, storage item, subscription, or rule group.; `note` (JsonData-Input|null): Structured auxiliary metadata stored with the record.; `pri` (integer|null; default `0`): Site search priority; lower or higher ordering follows the existing site API convention.; `proxy` (integer|null; default `0`): Whether the site uses MoviePilot's configured proxy.; `public` (integer|null; default `0`): Whether the site is treated as a public indexer.; `render` (integer|null; default `0`): Whether site requests require browser rendering.; `rss` (string|null): Site RSS feed URL.; `timeout` (integer|null; default `15`): Per-request site timeout in seconds.; `token` (string|null): Site authentication token. Treat this value as a secret.; `ua` (string|null): Site User-Agent string used for authenticated requests.; `url` (string|null): Site, storage, or torrent URL represented by this field. @@ -153,6 +173,7 @@ Purpose: Update one configured site's complete settings. ### `site.userdata` `GET /api/v1/site/userdata/{site_id}`; policy effect: `safe_read`. Purpose: Read the latest account statistics collected from one site. +Identity scope: `admin`; required role: `system_admin`. - `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated. - `path_params`: `site_id*` (integer): Persistent site ID returned by site.list. - `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.; `workdate` (string|null): Date used when retrieving one site's historical user statistics. @@ -161,6 +182,7 @@ Purpose: Read the latest account statistics collected from one site. ### `site.userdata.latest` `GET /api/v1/site/userdata/latest`; policy effect: `safe_read`. Purpose: Read the latest collected account statistics for every configured site. +Identity scope: `admin`; required role: `system_admin`. - `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated. - `path_params`: none - `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result. @@ -169,6 +191,7 @@ Purpose: Read the latest collected account statistics for every configured site. ### `site.userdata.refresh` `POST /api/v1/site/userdata/{site_id}`; policy effect: `external_side_effect`. Purpose: Refresh and return account statistics for one configured site. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `site_id*` (integer): Persistent site ID returned by site.list. - `query`: none - `body`: none diff --git a/skills/moviepilot-api/api/slash.md b/skills/moviepilot-api/api/slash.md index 58d0e3d63..9e205e839 100644 --- a/skills/moviepilot-api/api/slash.md +++ b/skills/moviepilot-api/api/slash.md @@ -7,6 +7,7 @@ Slash-command discovery and execution. ### `slash.list` `GET /api/v1/message/agent/commands`; policy effect: `safe_read`. Purpose: List slash commands that the Agent may dispatch. +Identity scope: `admin`; required role: `system_admin`. - `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated. - `path_params`: none - `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result. @@ -15,6 +16,7 @@ Purpose: List slash commands that the Agent may dispatch. ### `slash.run` `POST /api/v1/message/agent/commands/run`; policy effect: `external_side_effect`. Purpose: Execute one complete slash command through MoviePilot messaging. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: `command*` (string): Complete slash command, including the leading slash and all arguments. diff --git a/skills/moviepilot-api/api/storage.md b/skills/moviepilot-api/api/storage.md index 6ef36f823..b43e3ce9a 100644 --- a/skills/moviepilot-api/api/storage.md +++ b/skills/moviepilot-api/api/storage.md @@ -7,6 +7,7 @@ Storage provider settings, browsing, directory management, rename, and delete op ### `storage.delete` `POST /api/v1/storage/delete`; policy effect: `destructive_write`. Purpose: Delete one exact file or directory from a configured storage provider. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: `basename` (string|null): Base filename without its parent path.; `children` (array|null): Child storage items nested below this item.; `drive_id` (string|null): Provider-native storage drive identifier.; `extension` (string|null): Filename extension, including or excluding the leading dot as returned by storage.; `fileid` (string|null): Provider-native storage item identifier.; `modify_time` (number|null): Storage item modification timestamp.; `name` (string|null): Human-readable name of the site, storage item, subscription, or rule group.; `parent_fileid` (string|null): Provider-native identifier of the parent storage directory.; `path` (string|null; default `/`): Storage or history path represented by this record.; `pickcode` (string|null): 115 storage pickcode associated with the item.; `size` (integer|null): File or torrent size in bytes.; `storage` (string|null; default `local`): Configured storage name or storage type used by the operation.; `thumbnail` (string|null): Thumbnail URL returned by the storage provider.; `type` (string|null): MoviePilot media or storage item type required by the selected operation.; `url` (string|null): Site, storage, or torrent URL represented by this field. @@ -14,6 +15,7 @@ Purpose: Delete one exact file or directory from a configured storage provider. ### `storage.list` `POST /api/v1/storage/agent/list`; policy effect: `safe_read`. Purpose: List files or directories from one configured storage location. +Identity scope: `bound_user`; required role: `user`. - `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated. - `path_params`: none - `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `keyword` (string|null): Case-insensitive substring used to discover settings or filter storage entries.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.; `sort` (string|null; default `updated_at`): Storage-list sort field or ordering expression. @@ -22,6 +24,7 @@ Purpose: List files or directories from one configured storage location. ### `storage.manage` `POST /api/v1/storage/manage`; policy effect: `external_side_effect`. Purpose: Run one provider-defined management action against an exact configured storage target. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: `action*` (string): Exact provider or workflow action identifier required by the selected operation.; `params` (object): Provider-defined JSON parameters for the selected authentication or storage action.; `target*` (string): Classification rule output containing a category ID and optional labels. @@ -29,6 +32,7 @@ Purpose: Run one provider-defined management action against an exact configured ### `storage.mkdir` `POST /api/v1/storage/mkdir`; policy effect: `reversible_write`. Purpose: Create a named child directory below one exact storage directory item. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: `name*` (string): Human-readable name of the site, storage item, subscription, or rule group. - `body`: `basename` (string|null): Base filename without its parent path.; `children` (array|null): Child storage items nested below this item.; `drive_id` (string|null): Provider-native storage drive identifier.; `extension` (string|null): Filename extension, including or excluding the leading dot as returned by storage.; `fileid` (string|null): Provider-native storage item identifier.; `modify_time` (number|null): Storage item modification timestamp.; `name` (string|null): Human-readable name of the site, storage item, subscription, or rule group.; `parent_fileid` (string|null): Provider-native identifier of the parent storage directory.; `path` (string|null; default `/`): Storage or history path represented by this record.; `pickcode` (string|null): 115 storage pickcode associated with the item.; `size` (integer|null): File or torrent size in bytes.; `storage` (string|null; default `local`): Configured storage name or storage type used by the operation.; `thumbnail` (string|null): Thumbnail URL returned by the storage provider.; `type` (string|null): MoviePilot media or storage item type required by the selected operation.; `url` (string|null): Site, storage, or torrent URL represented by this field. @@ -36,6 +40,7 @@ Purpose: Create a named child directory below one exact storage directory item. ### `storage.rename` `POST /api/v1/storage/rename`; policy effect: `reversible_write`. Purpose: Rename one exact storage item, optionally applying media-aware recursive renaming. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: `new_name*` (string): Replacement name for the existing filter-rule group.; `recursive` (boolean|null; default `False`): Apply media-aware renaming recursively to child files when true. - `body`: `basename` (string|null): Base filename without its parent path.; `children` (array|null): Child storage items nested below this item.; `drive_id` (string|null): Provider-native storage drive identifier.; `extension` (string|null): Filename extension, including or excluding the leading dot as returned by storage.; `fileid` (string|null): Provider-native storage item identifier.; `modify_time` (number|null): Storage item modification timestamp.; `name` (string|null): Human-readable name of the site, storage item, subscription, or rule group.; `parent_fileid` (string|null): Provider-native identifier of the parent storage directory.; `path` (string|null; default `/`): Storage or history path represented by this record.; `pickcode` (string|null): 115 storage pickcode associated with the item.; `size` (integer|null): File or torrent size in bytes.; `storage` (string|null; default `local`): Configured storage name or storage type used by the operation.; `thumbnail` (string|null): Thumbnail URL returned by the storage provider.; `type` (string|null): MoviePilot media or storage item type required by the selected operation.; `url` (string|null): Site, storage, or torrent URL represented by this field. @@ -43,6 +48,7 @@ Purpose: Rename one exact storage item, optionally applying media-aware recursiv ### `storage.settings` `GET /api/v1/storage/directories`; policy effect: `safe_read`. Purpose: Read configured directory or storage settings. +Identity scope: `admin`; required role: `system_admin`. - `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated. - `path_params`: none - `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `directory_type` (string; default `all`): Directory configuration subtype to return.; `name` (string|null): Human-readable name of the site, storage item, subscription, or rule group.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.; `storage_type` (string; default `all`): Configured storage provider type to return. diff --git a/skills/moviepilot-api/api/subscription.md b/skills/moviepilot-api/api/subscription.md index 5a6bb9967..b774f86c1 100644 --- a/skills/moviepilot-api/api/subscription.md +++ b/skills/moviepilot-api/api/subscription.md @@ -7,6 +7,7 @@ Subscription creation, search, refresh, sharing, following, history, files, and ### `subscription.add` `POST /api/v1/subscribe/`; policy effect: `reversible_write`. Purpose: Create one movie, TV, or music subscription. +Identity scope: `bound_user`; required role: `user`. - `path_params`: none - `query`: none - `body`: `audio_format` (string|null): Requested or recorded audio container or codec, such as FLAC or MP3.; `audio_quality` (string|null): Subscription audio-quality rule, such as hires, lossless, or lossy.; `backdrop` (string|null): Backdrop image URL stored with the media or subscription.; `best_version` (integer|null): Enable normal best-version upgrading when set to 1.; `best_version_full` (integer|null): Enable full best-version upgrading when set to 1.; `classification_policy_revision` (integer|null): Policy revision that produced the persisted classification snapshot.; `classification_rule_id` (string|null): Stable rule ID that selected the persisted classification category.; `classification_source` (string|null): Selection source recorded with the persisted classification snapshot.; `completed_episode` (integer|null): Highest episode number already completed for the subscription.; `completed_tracks` (integer|null): Number of distinct album tracks accepted for the subscription so far.; `current_audio_format` (string|null): Audio format of the best version currently held.; `current_bit_depth` (integer|null): Bit depth of the best version currently held.; `current_bitrate` (integer|null): Bitrate of the best version currently held.; `current_priority` (integer|null): Calculated priority of the best version currently held.; `current_sample_rate` (integer|null): Sample rate of the best version currently held.; `custom_words` (string|null): Custom recognition or rename words applied to this media workflow.; `date` (string|null): Record creation or completion timestamp used by the history item.; `description` (string|null): Human-readable media, torrent, or subscription description.; `downloader` (string|null): Configured downloader instance name.; `effect` (string|null): Video or release-effect filter expression used by the subscription.; `episode_group` (string|null): TMDB episode-group identifier used for alternate episode ordering.; `episode_priority` (object|null): Per-episode best-version priority state.; `exclude` (string|null): Regular expression or filter expression that rejects matching releases.; `execution_status` (SubscriptionExecutionStatus|null): Current subscription execution status returned with the subscription snapshot.; `filter` (string|null): Named filter rule or rule expression applied to this site or subscription.; `filter_groups` (array|null): Ordered filter-rule group names applied to the subscription.; `id` (integer|null): Persistent database identifier of the supplied record.; `include` (string|null): Regular expression or filter expression that a release must match.; `keyword` (string|null): Case-insensitive substring used to discover settings or filter storage entries.; `lack_episode` (integer|null; default `0`): Number of episodes still missing from the subscription.; `last_search` (string|null): Read-only UTC timestamp of the most recent subscription search attempt.; `last_update` (string|null): Timestamp of the subscription's most recent update.; `media_category` (string|null): MoviePilot library category assigned to the media.; `media_category_id` (string|null): Stable classification category ID; preserve it separately from the current category path snapshot.; `media_id` (string|null): Source-native media ID. Always pair it with the exact media_source returned by search.; `media_source` (MediaSource|null): Metadata source identifier. Preserve the exact value returned with media_id.; `min_bit_depth` (integer|null): Minimum acceptable audio bit depth in bits.; `min_bitrate` (integer|null): Minimum acceptable audio bitrate in bits per second.; `min_sample_rate` (integer|null): Minimum acceptable audio sample rate in hertz.; `music_type` (string|null): Music identity level: recording, album, or artist where supported.; `name` (string|null): Human-readable name of the site, storage item, subscription, or rule group.; `note` (array|null): Structured auxiliary metadata stored with the record.; `poster` (string|null): Poster image URL stored with the media or subscription.; `quality` (string|null): Video or release quality filter expression.; `resolution` (string|null): Video resolution filter expression, such as 1080p or 2160p.; `save_path` (string|null): Configured downloader-side save path, or the exact name (alias) of a configured download directory, for the download or subscription.; `search_imdbid` (integer|null; default `0`): Use IMDb identity during subscription search when set to 1.; `search_interval` (integer|null): Scheduled search interval in whole hours (1-8760); null uses the system interval.; `season` (integer|null): Season number used by the media, search, subscription, or transfer operation.; `sites` (array|null): Exact site IDs included in the search or subscription scope.; `start_episode` (integer|null; default `0`): First episode number requested by the subscription.; `state` (string|null): Current site, subscription, marketplace, or transfer state filter.; `total_episode` (integer|null; default `0`): Expected total episode count for the subscription.; `total_tracks` (integer|null): Expected or recorded track count for a music item.; `type` (string|null): MoviePilot media or storage item type required by the selected operation.; `username` (string|null): MoviePilot or site username required by the selected operation.; `vote` (number|null; default `0.0`): Media vote average stored with the subscription.; `year` (string|null): Release or premiere year used to disambiguate the media title. @@ -14,6 +15,7 @@ Purpose: Create one movie, TV, or music subscription. ### `subscription.delete` `DELETE /api/v1/subscribe/{subscribe_id}`; policy effect: `destructive_write`. Purpose: Delete one active subscription. +Identity scope: `bound_user`; required role: `user`. - `path_params`: `subscribe_id*` (integer): Persistent subscription ID returned by subscription.list. - `query`: none - `body`: none @@ -21,6 +23,7 @@ Purpose: Delete one active subscription. ### `subscription.delete_by_media` `DELETE /api/v1/subscribe/media/{media_id}`; policy effect: `destructive_write`. Purpose: Delete accessible subscriptions matching one canonical media identity. +Identity scope: `bound_user`; required role: `user`. - `path_params`: `media_id*` (string): Source-native media ID. Always pair it with the exact media_source returned by search. - `query`: `media_source*` (MediaSource): Metadata source identifier. Preserve the exact value returned with media_id.; `music_type` (string|null): Music identity level: recording, album, or artist where supported.; `season` (integer|null): Season number used by the media, search, subscription, or transfer operation. - `body`: none @@ -28,6 +31,7 @@ Purpose: Delete accessible subscriptions matching one canonical media identity. ### `subscription.execution.cancel` `PUT /api/v1/subscribe/execution/batches/{batch_id}/cancel`; policy effect: `external_side_effect`. Purpose: Request cancellation of one durable subscription-search batch before its download side-effect boundary. +Identity scope: `bound_user`; required role: `user`. - `path_params`: `batch_id*` (string): Stable subscription search batch identifier. - `query`: none - `body`: none @@ -35,6 +39,7 @@ Purpose: Request cancellation of one durable subscription-search batch before it ### `subscription.execution.get` `GET /api/v1/subscribe/execution/batches/{batch_id}`; policy effect: `safe_read`. Purpose: Read one durable subscription-search batch visible to the current user. +Identity scope: `bound_user`; required role: `user`. - `path_params`: `batch_id*` (string): Stable subscription search batch identifier. - `query`: none - `body`: none @@ -42,6 +47,7 @@ Purpose: Read one durable subscription-search batch visible to the current user. ### `subscription.execution.list` `GET /api/v1/subscribe/execution/batches`; policy effect: `safe_read`. Purpose: List recent durable subscription-search batches visible to the current user. +Identity scope: `bound_user`; required role: `user`. - `response`: `data` remains a list and `collection.result_count` reports the returned items. `collection.total_count` is omitted because this endpoint or its upstream source does not expose a total. - `path_params`: none - `query`: `limit` (integer; default `10`): Maximum number of recent subscription-search batches to return. @@ -50,6 +56,7 @@ Purpose: List recent durable subscription-search batches visible to the current ### `subscription.files` `GET /api/v1/subscribe/files/{subscribe_id}`; policy effect: `safe_read`. Purpose: Read local library and transfer-file coverage for one accessible subscription. +Identity scope: `bound_user`; required role: `user`. - `path_params`: `subscribe_id*` (integer): Persistent subscription ID returned by subscription.list. - `query`: none - `body`: none @@ -57,6 +64,7 @@ Purpose: Read local library and transfer-file coverage for one accessible subscr ### `subscription.find` `GET /api/v1/subscribe/media/{media_id}`; policy effect: `safe_read`. Purpose: Find one accessible subscription by canonical media identity and optional season. For movies and TV, if exact identity misses and mtype/title are supplied, match across sources by type, normalized title, optional season, and year. An exact year ranks first; a subscription with no year may match when the card has a year, while a card with no year only matches a subscription with no year. Music uses exact identity only. +Identity scope: `bound_user`; required role: `user`. - `path_params`: `media_id*` (string): Source-native media ID. Always pair it with the exact media_source returned by search. - `query`: `media_source*` (MediaSource): Metadata source identifier. Preserve the exact value returned with media_id.; `mtype` (MediaType|null): MoviePilot media type or subscription-history category required by the operation.; `music_type` (string|null): Music identity level: recording, album, or artist where supported.; `season` (integer|null): Season number used by the media, search, subscription, or transfer operation.; `title` (string|null): Media, torrent, subscription, or history title used by the operation.; `year` (string|null): Release or premiere year used to disambiguate the media title. - `body`: none @@ -64,6 +72,7 @@ Purpose: Find one accessible subscription by canonical media identity and option ### `subscription.follow.add` `POST /api/v1/subscribe/follow`; policy effect: `reversible_write`. Purpose: Follow one subscription-sharing user by exact share user ID. +Identity scope: `bound_user`; required role: `user`. - `path_params`: none - `query`: `share_uid` (string|null): Exact MoviePilot Server sharing-user ID to follow or unfollow. - `body`: none @@ -71,6 +80,7 @@ Purpose: Follow one subscription-sharing user by exact share user ID. ### `subscription.follow.delete` `DELETE /api/v1/subscribe/follow`; policy effect: `reversible_write`. Purpose: Stop following one subscription-sharing user by exact share user ID. +Identity scope: `bound_user`; required role: `user`. - `path_params`: none - `query`: `share_uid` (string|null): Exact MoviePilot Server sharing-user ID to follow or unfollow. - `body`: none @@ -78,6 +88,7 @@ Purpose: Stop following one subscription-sharing user by exact share user ID. ### `subscription.follow.list` `GET /api/v1/subscribe/follow`; policy effect: `safe_read`. Purpose: List subscription-sharing user IDs followed by the current user. +Identity scope: `bound_user`; required role: `user`. - `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated. - `path_params`: none - `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result. @@ -86,6 +97,7 @@ Purpose: List subscription-sharing user IDs followed by the current user. ### `subscription.fork` `POST /api/v1/subscribe/fork`; policy effect: `external_side_effect`. Purpose: Create a local subscription from one shared subscription definition. +Identity scope: `bound_user`; required role: `user`. - `path_params`: none - `query`: none - `body`: `audio_format` (string|null): Requested or recorded audio container or codec, such as FLAC or MP3.; `audio_quality` (string|null): Subscription audio-quality rule, such as hires, lossless, or lossy.; `backdrop` (string|null): Backdrop image URL stored with the media or subscription.; `count` (integer|null; default `0`): Maximum number of records to return on the requested page.; `custom_words` (string|null): Custom recognition or rename words applied to this media workflow.; `date` (string|null): Record creation or completion timestamp used by the history item.; `description` (string|null): Human-readable media, torrent, or subscription description.; `effect` (string|null): Video or release-effect filter expression used by the subscription.; `episode_group` (string|null): TMDB episode-group identifier used for alternate episode ordering.; `exclude` (string|null): Regular expression or filter expression that rejects matching releases.; `id` (integer|null): Persistent database identifier of the supplied record.; `include` (string|null): Regular expression or filter expression that a release must match.; `keyword` (string|null): Case-insensitive substring used to discover settings or filter storage entries.; `media_category` (string|null): MoviePilot library category assigned to the media.; `media_category_id` (string|null): Stable classification category ID; preserve it separately from the current category path snapshot.; `media_id` (string|null): Source-native media ID. Always pair it with the exact media_source returned by search.; `media_source` (MediaSource|null): Metadata source identifier. Preserve the exact value returned with media_id.; `min_bit_depth` (integer|null): Minimum acceptable audio bit depth in bits.; `min_bitrate` (integer|null): Minimum acceptable audio bitrate in bits per second.; `min_sample_rate` (integer|null): Minimum acceptable audio sample rate in hertz.; `music_type` (string|null): Music identity level: recording, album, or artist where supported.; `name` (string|null): Human-readable name of the site, storage item, subscription, or rule group.; `poster` (string|null): Poster image URL stored with the media or subscription.; `quality` (string|null): Video or release quality filter expression.; `resolution` (string|null): Video resolution filter expression, such as 1080p or 2160p.; `season` (integer|null): Season number used by the media, search, subscription, or transfer operation.; `share_comment` (string|null): Optional explanatory comment published with a shared item.; `share_title` (string|null): Public title used when publishing a subscription or workflow.; `share_uid` (string|null): Exact MoviePilot Server sharing-user ID to follow or unfollow.; `share_user` (string|null): Public contributor name used when publishing a subscription or workflow.; `subscribe_id` (integer|null): Persistent subscription ID returned by subscription.list.; `total_episode` (integer|null; default `0`): Expected total episode count for the subscription.; `total_tracks` (integer|null): Expected or recorded track count for a music item.; `type` (string|null): MoviePilot media or storage item type required by the selected operation.; `vote` (number|null; default `0.0`): Media vote average stored with the subscription.; `year` (string|null): Release or premiere year used to disambiguate the media title. @@ -93,6 +105,7 @@ Purpose: Create a local subscription from one shared subscription definition. ### `subscription.get` `GET /api/v1/subscribe/{subscribe_id}`; policy effect: `safe_read`. Purpose: Read one accessible subscription by persistent subscription ID. +Identity scope: `bound_user`; required role: `user`. - `path_params`: `subscribe_id*` (integer): Persistent subscription ID returned by subscription.list. - `query`: none - `body`: none @@ -100,6 +113,7 @@ Purpose: Read one accessible subscription by persistent subscription ID. ### `subscription.history` `GET /api/v1/subscribe/history/{mtype}`; policy effect: `safe_read`. Purpose: List completed or archived subscription records. +Identity scope: `bound_user`; required role: `user`. - `response`: `data` remains a list and the endpoint's documented pagination or limit defaults remain in effect. `collection.result_count` reports the returned items and `collection.total_count` reports the exact total. For a count-only request, use the smallest valid page and read that metadata instead of querying the database after item truncation. - `path_params`: `mtype*` (string): MoviePilot media type or subscription-history category required by the operation. - `query`: `count` (integer|null; default `30`): Maximum number of records to return on the requested page.; `page` (integer|null; default `1`): One-based result page number. @@ -108,6 +122,7 @@ Purpose: List completed or archived subscription records. ### `subscription.history.delete` `DELETE /api/v1/subscribe/history/{history_id}`; policy effect: `destructive_write`. Purpose: Delete one accessible subscription-history record. +Identity scope: `bound_user`; required role: `user`. - `path_params`: `history_id*` (integer): Persistent transfer- or subscription-history ID returned by a history operation. - `query`: none - `body`: none @@ -115,6 +130,7 @@ Purpose: Delete one accessible subscription-history record. ### `subscription.list` `GET /api/v1/subscribe/`; policy effect: `safe_read`. Purpose: List active subscriptions. +Identity scope: `bound_user`; required role: `user`. - `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated. - `path_params`: none - `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result. @@ -123,6 +139,7 @@ Purpose: List active subscriptions. ### `subscription.metadata.refresh` `POST /api/v1/subscribe/check`; policy effect: `external_side_effect`. Purpose: Start a system-wide refresh of subscription TMDB metadata. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: `type` (MediaType|null): MoviePilot media or storage item type required by the selected operation. - `body`: none @@ -130,6 +147,7 @@ Purpose: Start a system-wide refresh of subscription TMDB metadata. ### `subscription.popular` `GET /api/v1/subscribe/popular`; policy effect: `safe_read`. Purpose: List globally popular subscriptions with filters and pagination. +Identity scope: `public`; required role: `user`. - `response`: `data` remains a list and `collection.result_count` reports the returned items. `collection.total_count` is omitted because this endpoint or its upstream source does not expose a total. - `path_params`: none - `query`: `count` (integer|null; default `30`): Maximum number of records to return on the requested page.; `genre_id` (integer|null): Genre identifier used to filter shared or popular subscriptions.; `max_rating` (number|null): Maximum rating used to filter shared or popular subscriptions.; `min_rating` (number|null): Minimum rating used to filter shared or popular subscriptions.; `min_sub` (integer|null): Minimum subscriber count used to filter popular subscriptions.; `page` (integer|null; default `1`): One-based result page number.; `sort_type` (string|null): Ascending or descending order used by shared or popular subscriptions.; `stype*` (string): Popular-subscription category requested by the endpoint. @@ -138,6 +156,7 @@ Purpose: List globally popular subscriptions with filters and pagination. ### `subscription.refresh` `POST /api/v1/subscribe/refresh`; policy effect: `external_side_effect`. Purpose: Start the configured system-wide subscription refresh job. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: `type` (MediaType|null): MoviePilot media or storage item type required by the selected operation. - `body`: none @@ -145,6 +164,7 @@ Purpose: Start the configured system-wide subscription refresh job. ### `subscription.reset` `POST /api/v1/subscribe/reset/{subid}`; policy effect: `reversible_write`. Purpose: Reset one accessible subscription so it can be processed again. +Identity scope: `bound_user`; required role: `user`. - `path_params`: `subid*` (integer): Persistent subscription ID whose status or processing state will change. - `query`: none - `body`: none @@ -152,6 +172,7 @@ Purpose: Reset one accessible subscription so it can be processed again. ### `subscription.search` `POST /api/v1/subscribe/search/{subscribe_id}`; policy effect: `external_side_effect`. Purpose: Run an immediate search for one existing subscription. +Identity scope: `bound_user`; required role: `user`. An explicit search can search and download once for a paused subscription without enabling it. State `S` stops automatic searches, including tasks already queued or waiting for site recovery; cancel the execution batch to stop an explicit search. Already submitted downloads remain active. @@ -162,6 +183,7 @@ cancel the execution batch to stop an explicit search. Already submitted downloa ### `subscription.search_all` `POST /api/v1/subscribe/search`; policy effect: `external_side_effect`. Purpose: Start immediate searches for all subscriptions accessible to the current user. +Identity scope: `bound_user`; required role: `user`. - `path_params`: none - `query`: `type` (MediaType|null): MoviePilot media or storage item type required by the selected operation. - `body`: none @@ -169,6 +191,7 @@ Purpose: Start immediate searches for all subscriptions accessible to the curren ### `subscription.share` `POST /api/v1/subscribe/share`; policy effect: `external_side_effect`. Purpose: Publish one accessible subscription to the MoviePilot sharing service. +Identity scope: `bound_user`; required role: `user`. - `path_params`: none - `query`: none - `body`: `audio_format` (string|null): Requested or recorded audio container or codec, such as FLAC or MP3.; `audio_quality` (string|null): Subscription audio-quality rule, such as hires, lossless, or lossy.; `backdrop` (string|null): Backdrop image URL stored with the media or subscription.; `count` (integer|null; default `0`): Maximum number of records to return on the requested page.; `custom_words` (string|null): Custom recognition or rename words applied to this media workflow.; `date` (string|null): Record creation or completion timestamp used by the history item.; `description` (string|null): Human-readable media, torrent, or subscription description.; `effect` (string|null): Video or release-effect filter expression used by the subscription.; `episode_group` (string|null): TMDB episode-group identifier used for alternate episode ordering.; `exclude` (string|null): Regular expression or filter expression that rejects matching releases.; `id` (integer|null): Persistent database identifier of the supplied record.; `include` (string|null): Regular expression or filter expression that a release must match.; `keyword` (string|null): Case-insensitive substring used to discover settings or filter storage entries.; `media_category` (string|null): MoviePilot library category assigned to the media.; `media_category_id` (string|null): Stable classification category ID; preserve it separately from the current category path snapshot.; `media_id` (string|null): Source-native media ID. Always pair it with the exact media_source returned by search.; `media_source` (MediaSource|null): Metadata source identifier. Preserve the exact value returned with media_id.; `min_bit_depth` (integer|null): Minimum acceptable audio bit depth in bits.; `min_bitrate` (integer|null): Minimum acceptable audio bitrate in bits per second.; `min_sample_rate` (integer|null): Minimum acceptable audio sample rate in hertz.; `music_type` (string|null): Music identity level: recording, album, or artist where supported.; `name` (string|null): Human-readable name of the site, storage item, subscription, or rule group.; `poster` (string|null): Poster image URL stored with the media or subscription.; `quality` (string|null): Video or release quality filter expression.; `resolution` (string|null): Video resolution filter expression, such as 1080p or 2160p.; `season` (integer|null): Season number used by the media, search, subscription, or transfer operation.; `share_comment` (string|null): Optional explanatory comment published with a shared item.; `share_title` (string|null): Public title used when publishing a subscription or workflow.; `share_uid` (string|null): Exact MoviePilot Server sharing-user ID to follow or unfollow.; `share_user` (string|null): Public contributor name used when publishing a subscription or workflow.; `subscribe_id` (integer|null): Persistent subscription ID returned by subscription.list.; `total_episode` (integer|null; default `0`): Expected total episode count for the subscription.; `total_tracks` (integer|null): Expected or recorded track count for a music item.; `type` (string|null): MoviePilot media or storage item type required by the selected operation.; `vote` (number|null; default `0.0`): Media vote average stored with the subscription.; `year` (string|null): Release or premiere year used to disambiguate the media title. @@ -176,6 +199,7 @@ Purpose: Publish one accessible subscription to the MoviePilot sharing service. ### `subscription.share.delete` `DELETE /api/v1/subscribe/share/{share_id}`; policy effect: `external_side_effect`. Purpose: Delete one shared-subscription publication by share ID. +Identity scope: `bound_user`; required role: `user`. - `path_params`: `share_id*` (integer): Persistent MoviePilot Server share ID returned by a share-list operation. - `query`: none - `body`: none @@ -183,6 +207,7 @@ Purpose: Delete one shared-subscription publication by share ID. ### `subscription.share.statistics` `GET /api/v1/subscribe/share/statistics`; policy effect: `safe_read`. Purpose: Read aggregate contribution and reuse counts for subscription sharers. +Identity scope: `public`; required role: `user`. - `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated. - `path_params`: none - `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result. @@ -191,6 +216,7 @@ Purpose: Read aggregate contribution and reuse counts for subscription sharers. ### `subscription.shares` `GET /api/v1/subscribe/shares`; policy effect: `safe_read`. Purpose: List shared subscriptions with filters and pagination. +Identity scope: `public`; required role: `user`. - `response`: `data` remains a list and `collection.result_count` reports the returned items. `collection.total_count` is omitted because this endpoint or its upstream source does not expose a total. - `path_params`: none - `query`: `count` (integer|null; default `30`): Maximum number of records to return on the requested page.; `genre_id` (integer|null): Genre identifier used to filter shared or popular subscriptions.; `max_rating` (number|null): Maximum rating used to filter shared or popular subscriptions.; `min_rating` (number|null): Minimum rating used to filter shared or popular subscriptions.; `name` (string|null): Human-readable name of the site, storage item, subscription, or rule group.; `page` (integer|null; default `1`): One-based result page number.; `sort_type` (string|null): Ascending or descending order used by shared or popular subscriptions. @@ -199,6 +225,7 @@ Purpose: List shared subscriptions with filters and pagination. ### `subscription.status.update` `PUT /api/v1/subscribe/status/{subid}`; policy effect: `reversible_write`. Purpose: Set one accessible subscription to running, paused, or stopped state. +Identity scope: `bound_user`; required role: `user`. - `path_params`: `subid*` (integer): Persistent subscription ID whose status or processing state will change. - `query`: `state*` (string): Current site, subscription, marketplace, or transfer state filter. - `body`: none @@ -206,6 +233,7 @@ Purpose: Set one accessible subscription to running, paused, or stopped state. ### `subscription.update` `PUT /api/v1/subscribe/`; policy effect: `reversible_write`. Purpose: Update one existing movie, TV, or music subscription. +Identity scope: `bound_user`; required role: `user`. - Omitted fields keep their stored values. Send JSON `null` to clear `custom_words`, `keyword`, `save_path`, `episode_group`, `downloader`, `min_bitrate`, `min_bit_depth`, `min_sample_rate`, `filter`, `include`, `exclude`, `quality`, `resolution`, `effect`, `audio_quality`, or `audio_format`. Form submissions using `""` for these fields are also accepted as explicit clearing. Nonempty strings are preserved without trimming, and numeric `0` remains a value. Name, type, season, and episode counts retain their existing normalization; submit `media_source` and `media_id` together when changing or clearing media identity. - `path_params`: none - `query`: none @@ -214,6 +242,7 @@ Purpose: Update one existing movie, TV, or music subscription. ### `subscription.user.list` `GET /api/v1/subscribe/user/{username}`; policy effect: `safe_read`. Purpose: List public subscriptions owned by one accessible MoviePilot username. +Identity scope: `bound_user`; required role: `user`. - `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated. - `path_params`: `username*` (string): MoviePilot or site username required by the selected operation. - `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result. diff --git a/skills/moviepilot-api/api/subtitle.md b/skills/moviepilot-api/api/subtitle.md index 380d421e8..bf40a06de 100644 --- a/skills/moviepilot-api/api/subtitle.md +++ b/skills/moviepilot-api/api/subtitle.md @@ -7,6 +7,7 @@ Subtitle title and media search. ### `subtitle.search.media` `GET /api/v1/search/subtitle/media/{media_id}`; policy effect: `external_side_effect`. Purpose: Search subtitle providers for one canonical media identity and optional season or episode. +Identity scope: `bound_user`; required role: `user`. - `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated. - `path_params`: `media_id*` (string): Source-native media ID. Always pair it with the exact media_source returned by search. - `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `episode` (string|null): Episode number used to narrow a subtitle or media search.; `media_source*` (MediaSource): Metadata source identifier. Preserve the exact value returned with media_id.; `mtype` (string|null): MoviePilot media type or subscription-history category required by the operation.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.; `season` (string|null): Season number used by the media, search, subscription, or transfer operation.; `sites` (string|null): Exact site IDs included in the search or subscription scope. @@ -15,6 +16,7 @@ Purpose: Search subtitle providers for one canonical media identity and optional ### `subtitle.search.title` `GET /api/v1/search/subtitle/title`; policy effect: `external_side_effect`. Purpose: Search subtitle providers from a free-form title and optional media filters. +Identity scope: `bound_user`; required role: `user`. - `response`: `data` remains a list and `collection.result_count` reports the returned items. `collection.total_count` is omitted because this endpoint or its upstream source does not expose a total. - `path_params`: none - `query`: `keyword` (string|null): Case-insensitive substring used to discover settings or filter storage entries.; `page` (integer|null; default `0`): One-based result page number.; `sites` (string|null): Exact site IDs included in the search or subscription scope. diff --git a/skills/moviepilot-api/api/system.md b/skills/moviepilot-api/api/system.md index 69d91ee98..31740382c 100644 --- a/skills/moviepilot-api/api/system.md +++ b/skills/moviepilot-api/api/system.md @@ -7,6 +7,7 @@ Version, update, restart, module, network, and usage operations. ### `system.module.catalog` `GET /api/v1/system/module-catalog`; policy effect: `safe_read`. Purpose: List MoviePilot host modules and service-type options for UI selectors. +Identity scope: `bound_user`; required role: `user`. - `path_params`: none - `query`: none - `body`: none @@ -14,6 +15,7 @@ Purpose: List MoviePilot host modules and service-type options for UI selectors. ### `system.module.list` `GET /api/v1/system/modulelist`; policy effect: `safe_read`. Purpose: List enabled MoviePilot module IDs and localized names. +Identity scope: `bound_user`; required role: `user`. - `path_params`: none - `query`: none - `body`: none @@ -21,6 +23,7 @@ Purpose: List enabled MoviePilot module IDs and localized names. ### `system.module.settings` `GET /api/v1/system/module-settings`; policy effect: `safe_read`. Purpose: List built-in MoviePilot modules that can be enabled or disabled by an administrator. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: none @@ -28,13 +31,15 @@ Purpose: List built-in MoviePilot modules that can be enabled or disabled by an ### `system.module.test` `GET /api/v1/system/moduletest/{moduleid}`; policy effect: `external_side_effect`. Purpose: Run the built-in availability test for one enabled MoviePilot module. +Identity scope: `bound_user`; required role: `user`. - `path_params`: `moduleid*` (string): Exact loaded module ID returned by system.module.list. - `query`: none - `body`: none ### `system.network.targets` `GET /api/v1/system/nettest/targets`; policy effect: `safe_read`. -Purpose: List approved built-in network-test targets without exposing their request URLs. +Purpose: List approved network-test targets with sanitized destination addresses, without exposing request paths, query parameters, or credentials. +Identity scope: `bound_user`; required role: `user`. - `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated. - `path_params`: none - `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result. @@ -43,6 +48,7 @@ Purpose: List approved built-in network-test targets without exposing their requ ### `system.network.test` `GET /api/v1/system/nettest`; policy effect: `external_side_effect`. Purpose: Test connectivity to one approved target or the legacy constrained URL input. +Identity scope: `bound_user`; required role: `user`. - `path_params`: none - `query`: `include` (string|null): Regular expression or filter expression that a release must match.; `target_id` (string|null): Approved built-in network-test target ID returned by system.network.targets.; `url` (string|null): Site, storage, or torrent URL represented by this field. - `body`: none @@ -50,6 +56,7 @@ Purpose: Test connectivity to one approved target or the legacy constrained URL ### `system.restart` `GET /api/v1/system/restart`; policy effect: `external_side_effect`. Purpose: Restart the running MoviePilot process. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: none @@ -57,6 +64,7 @@ Purpose: Restart the running MoviePilot process. ### `system.update.check` `POST /api/v1/system/update/check`; policy effect: `external_side_effect`. Purpose: Check for the latest stable MoviePilot v3 application release and current-platform site resources. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: none @@ -64,6 +72,7 @@ Purpose: Check for the latest stable MoviePilot v3 application release and curre ### `system.update.download` `POST /api/v1/system/update/download`; policy effect: `external_side_effect`. Purpose: Start downloading and verifying one selected application or site-resource update in the background. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body` (SystemUpdateRequest|null): Request value for system.update.download. Start downloading and verifying one selected application or site-resource update in the background. Use the exact type and fields below. @@ -71,6 +80,7 @@ Purpose: Start downloading and verifying one selected application or site-resour ### `system.update.install` `POST /api/v1/system/update/install`; policy effect: `external_side_effect`. Purpose: Install one selected already downloaded and verified application or site-resource update, then restart MoviePilot. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body` (SystemUpdateRequest|null): Request value for system.update.install. Install one selected already downloaded and verified application or site-resource update, then restart MoviePilot. Use the exact type and fields below. @@ -78,6 +88,7 @@ Purpose: Install one selected already downloaded and verified application or sit ### `system.update.status` `GET /api/v1/system/update/status`; policy effect: `safe_read`. Purpose: Read application and site-resource update checks, downloads, verification, or install state. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: none @@ -85,6 +96,7 @@ Purpose: Read application and site-resource update checks, downloads, verificati ### `system.upgrade.dev` `POST /api/v1/system/upgrade`; policy effect: `external_side_effect`. Purpose: Update to the current v3 development branch and restart MoviePilot. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body*` (string=dev): Literal dev. Release updates must use the separate check, download, and install operations. @@ -92,6 +104,7 @@ Purpose: Update to the current v3 development branch and restart MoviePilot. ### `system.usage.statistics` `GET /api/v1/system/usage/statistic`; policy effect: `safe_read`. Purpose: Read the installation version and runtime usage report available to the current user. +Identity scope: `bound_user`; required role: `user`. - `path_params`: none - `query`: none - `body`: none @@ -99,6 +112,7 @@ Purpose: Read the installation version and runtime usage report available to the ### `system.versions` `GET /api/v1/system/versions`; policy effect: `safe_read`. Purpose: List available MoviePilot GitHub releases. +Identity scope: `admin`; required role: `system_admin`. - `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated. - `path_params`: none - `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result. diff --git a/skills/moviepilot-api/api/torrent.md b/skills/moviepilot-api/api/torrent.md index 9550aeddc..944787ea5 100644 --- a/skills/moviepilot-api/api/torrent.md +++ b/skills/moviepilot-api/api/torrent.md @@ -7,6 +7,7 @@ Torrent-cache inspection, refresh, re-identification, and deletion. ### `torrent.cache.clear` `DELETE /api/v1/torrent/cache`; policy effect: `destructive_write`. Purpose: Delete every cached torrent context. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: none @@ -14,6 +15,7 @@ Purpose: Delete every cached torrent context. ### `torrent.cache.delete` `DELETE /api/v1/torrent/cache/{domain}/{torrent_hash}`; policy effect: `destructive_write`. Purpose: Delete one cached torrent context by site domain and cache hash. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `domain*` (string): Site hostname or domain used for matching and requests.; `torrent_hash*` (string): Cache hash returned by torrent.cache.get for one exact site-domain entry. - `query`: none - `body`: none @@ -21,6 +23,7 @@ Purpose: Delete one cached torrent context by site domain and cache hash. ### `torrent.cache.get` `GET /api/v1/torrent/cache`; policy effect: `safe_read`. Purpose: Inspect cached torrent contexts and their recognized media identities. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: none @@ -28,6 +31,7 @@ Purpose: Inspect cached torrent contexts and their recognized media identities. ### `torrent.cache.refresh` `POST /api/v1/torrent/cache/refresh`; policy effect: `external_side_effect`. Purpose: Refresh torrent caches from configured RSS or spider sources. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: none @@ -35,6 +39,7 @@ Purpose: Refresh torrent caches from configured RSS or spider sources. ### `torrent.cache.reidentify` `POST /api/v1/torrent/cache/reidentify/{domain}/{torrent_hash}`; policy effect: `reversible_write`. Purpose: Replace or recompute the media identity for one cached torrent context. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `domain*` (string): Site hostname or domain used for matching and requests.; `torrent_hash*` (string): Cache hash returned by torrent.cache.get for one exact site-domain entry. - `query`: `media_id` (string|null): Source-native media ID. Always pair it with the exact media_source returned by search.; `media_source` (MediaSource|null): Metadata source identifier. Preserve the exact value returned with media_id.; `music_type` (string(recording,album)|null): Music identity level: recording, album, or artist where supported. - `body`: none diff --git a/skills/moviepilot-api/api/transfer.md b/skills/moviepilot-api/api/transfer.md index 34af0a2f6..a089b6116 100644 --- a/skills/moviepilot-api/api/transfer.md +++ b/skills/moviepilot-api/api/transfer.md @@ -31,6 +31,7 @@ other albums with the same display name. Unsupported packages appear as failed i ### `transfer.episode_format.recommend` `POST /api/v1/transfer/episode-format/recommend`; policy effect: `safe_read`. Purpose: Recommend an episode-number extraction template from supplied file samples. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: `fileitem` (FileItem-Input|null): One complete source storage item returned by storage.list.; `fileitems` (array|null): Additional source storage items included in the same manual transfer. @@ -38,13 +39,15 @@ Purpose: Recommend an episode-number extraction template from supplied file samp ### `transfer.file` `POST /api/v1/transfer/manual`; policy effect: `external_side_effect`. Purpose: Run MoviePilot's manual file-transfer and organization workflow. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: `background` (boolean|null; default `False`): Run the transfer asynchronously and return before completion. -- `body`: `episode_detail` (string|null): Episode mapping details used by manual transfer.; `episode_format` (string|null): Episode-number formatting rule used by manual transfer.; `episode_group` (string|null): TMDB episode-group identifier used for alternate episode ordering.; `episode_offset` (string|null): Integer offset added to detected episode numbers.; `episode_part` (string|null): Episode part number used when one episode is split across files.; `fileitem` (FileItem-Input): One complete source storage item returned by storage.list.; `fileitems` (array|null): Additional source storage items included in the same manual transfer.; `from_history` (boolean|null; default `False`): Treat the transfer input as originating from an existing history record.; `library_category_folder` (boolean|null): Create or use a category-level folder in the target library.; `library_type_folder` (boolean|null): Create or use a media-type folder in the target library.; `logid` (integer|null): One download-history or transfer-log identifier used by manual transfer.; `logids` (array|null): Multiple download-history or transfer-log identifiers included in manual transfer.; `media_id` (string|null): Source-native media ID. Always pair it with the exact media_source returned by search.; `media_source` (MediaSource|null): Metadata source identifier. Preserve the exact value returned with media_id.; `min_filesize` (integer|null; default `0`): Minimum source file size accepted by manual transfer, in bytes.; `music_release_regions` (array|null): Optional ISO 3166-1 release-region priority for music organization.; `music_release_scripts` (array|null): Optional ISO 15924 script priority for music organization.; `music_type` (string(recording,album)|null): Music identity level: recording, album, or artist where supported.; `musicbrainz_release_id` (string|null): Optional exact MusicBrainz Release UUID belonging to the selected album Release Group; never use it as media_id.; `preview` (boolean|null; default `False`): Validate and preview manual-transfer output without committing file changes.; `reorganize` (boolean|null; default `False`): Allow manual transfer to organize an item that was already processed.; `scrape` (boolean|null; default `False`): Generate metadata and images after manual transfer.; `season` (integer|null): Season number used by the media, search, subscription, or transfer operation.; `skip_success` (boolean; default `False`): Skip files already recorded as successfully organized.; `target_path` (string|null): Destination path used by manual transfer.; `target_storage` (string|null): Configured storage name receiving the manual transfer.; `transfer_type` (string|null): Manual-transfer mode, such as move, copy, link, or softlink.; `type_name` (string|null): Explicit media type name used when source IDs alone are ambiguous. +- `body`: `episode_detail` (string|null): Episode mapping details used by manual transfer.; `episode_format` (string|null): Episode-number formatting rule used by manual transfer.; `episode_group` (string|null): TMDB episode-group identifier used for alternate episode ordering.; `episode_offset` (string|null): Integer offset added to detected episode numbers.; `episode_part` (string|null): Episode part number used when one episode is split across files.; `fileitem` (FileItem-Input): One complete source storage item returned by storage.list.; `fileitems` (array|null): Additional source storage items included in the same manual transfer.; `from_history` (boolean|null; default `False`): Treat the transfer input as originating from an existing history record.; `library_category_folder` (boolean|null): Create or use a category-level folder in the target library.; `library_type_folder` (boolean|null): Create or use a media-type folder in the target library.; `logid` (integer|null): One download-history or transfer-log identifier used by manual transfer.; `logids` (array|null): Multiple download-history or transfer-log identifiers included in manual transfer.; `media_id` (string|null): Source-native media ID. Always pair it with the exact media_source returned by search.; `media_source` (MediaSource|null): Metadata source identifier. Preserve the exact value returned with media_id.; `min_filesize` (integer|null; default `0`): Minimum source file size accepted by manual transfer, in bytes.; `music_cue_enable` (boolean|null): Optional per-subscription CUE organization setting; omit to inherit the system default.; `music_release_regions` (array|null): Optional ISO 3166-1 release-region priority for music organization.; `music_release_scripts` (array|null): Optional ISO 15924 script priority for music organization.; `music_type` (string(recording,album)|null): Music identity level: recording, album, or artist where supported.; `musicbrainz_release_id` (string|null): Optional exact MusicBrainz Release UUID belonging to the selected album Release Group; never use it as media_id.; `preview` (boolean|null; default `False`): Validate and preview manual-transfer output without committing file changes.; `reorganize` (boolean|null; default `False`): Allow manual transfer to organize an item that was already processed.; `scrape` (boolean|null; default `False`): Generate metadata and images after manual transfer.; `season` (integer|null): Season number used by the media, search, subscription, or transfer operation.; `skip_success` (boolean; default `False`): Skip files already recorded as successfully organized.; `target_path` (string|null): Destination path used by manual transfer.; `target_storage` (string|null): Configured storage name receiving the manual transfer.; `transfer_type` (string|null): Manual-transfer mode, such as move, copy, link, or softlink.; `type_name` (string|null): Explicit media type name used when source IDs alone are ambiguous. ### `transfer.history` `GET /api/v1/history/transfer`; policy effect: `safe_read`. Purpose: List file-transfer history with filters and pagination. +Identity scope: `bound_user`; required role: `user`. - `response`: structured page object; items stay in `data.list` and the exact total stays in `data.total`. - `path_params`: none - `query`: `count` (integer|null; default `30`): Maximum number of records to return on the requested page.; `page` (integer|null; default `1`): One-based result page number.; `status` (boolean|null): Transfer success status used to filter history or describe a record.; `title` (string|null): Media, torrent, subscription, or history title used by the operation. @@ -53,6 +56,7 @@ Purpose: List file-transfer history with filters and pagination. ### `transfer.history.clear` `DELETE /api/v1/history/transfer/all`; policy effect: `destructive_write`. Purpose: Delete legacy transfer-history records while leaving files and durable failed-task records untouched. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: none @@ -60,6 +64,7 @@ Purpose: Delete legacy transfer-history records while leaving files and durable ### `transfer.history.delete` `DELETE /api/v1/history/transfer`; policy effect: `destructive_write`. Purpose: Delete one transfer-history record and optionally remove files. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: `deletedest` (boolean|null; default `False`): Also delete the organized destination files when deleting transfer history.; `deletesrc` (boolean|null; default `False`): Also delete the recorded source files when deleting transfer history. - `body`: `audio_format` (string|null): Requested or recorded audio container or codec, such as FLAC or MP3.; `audio_lossless` (boolean|null): Whether the recorded audio result is lossless.; `auto_paused` (boolean; default `False`): Whether a failed transfer automatically paused the related subscription.; `bit_depth` (integer|null): Recorded audio bit depth in bits.; `bitrate` (integer|null): Recorded audio bitrate in bits per second.; `category` (string|null): MoviePilot media category or filter-group category, depending on the operation.; `classification_policy_revision` (integer|null): Policy revision that produced the persisted classification snapshot.; `classification_rule_id` (string|null): Stable rule ID that selected the persisted classification category.; `classification_source` (string|null): Selection source recorded with the persisted classification snapshot.; `cleanup_error` (string|null): Error recorded while cleaning up a transfer source or destination.; `cleanup_status` (string|null): Current status of transfer-file cleanup after an operation.; `date` (string|null): Record creation or completion timestamp used by the history item.; `dest` (string|null): Organized destination path recorded in transfer history.; `dest_fileitem` (JsonData-Input|null): Serialized destination storage item recorded by the transfer.; `dest_storage` (string|null): Configured storage name containing the organized destination.; `download_hash` (string|null): Provider-native torrent hash associated with the record.; `episode_group` (string|null): TMDB episode-group identifier used for alternate episode ordering.; `episodes` (string|null): Episode-number expression recorded in history, such as E01-E03.; `errmsg` (string|null): Error message recorded for a failed transfer.; `failure_stage` (string|null): Transfer or workflow stage at which the operation failed.; `files` (JsonData-Input|null): Serialized list of files recorded by the history item.; `id*` (integer): Persistent database identifier of the supplied record.; `image` (string|null): Image URL stored with the history record.; `media_category_id` (string|null): Stable classification category ID; preserve it separately from the current category path snapshot.; `media_id` (string|null): Source-native media ID. Always pair it with the exact media_source returned by search.; `media_source` (MediaSource|null): Metadata source identifier. Preserve the exact value returned with media_id.; `mode` (string|null): Operation mode; music.explore accepts chart or fresh, while transfer history records move, copy, link, or softlink.; `music_type` (string|null): Music identity level: recording, album, or artist where supported.; `recovery_action` (string|null): Recovery action reported for a failed or partially completed transfer.; `retry_count` (integer|null): Number of retry attempts already used by the operation.; `retry_exhausted` (boolean; default `False`): Whether the operation has used all configured retry attempts.; `sample_rate` (integer|null): Recorded audio sample rate in hertz.; `seasons` (string|null): Season-number expression recorded in history.; `src` (string|null): Source path recorded in transfer history.; `src_fileitem` (JsonData-Input|null): Serialized source storage item recorded by the transfer.; `src_storage` (string|null): Configured storage name containing the transfer source.; `status` (boolean; default `True`): Transfer success status used to filter history or describe a record.; `title` (string|null): Media, torrent, subscription, or history title used by the operation.; `total_tracks` (integer|null): Expected or recorded track count for a music item.; `transfer_task_id` (string|null): Stable durable transfer-task ID associated with the history record.; `type` (string|null): MoviePilot media or storage item type required by the selected operation.; `year` (string|null): Release or premiere year used to disambiguate the media title. @@ -67,6 +72,7 @@ Purpose: Delete one transfer-history record and optionally remove files. ### `transfer.history.redo` `POST /api/v1/history/transfer/{history_id}/ai-redo`; policy effect: `external_side_effect`. Purpose: Start AI-assisted reorganization for one transfer-history record. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `history_id*` (integer): Persistent transfer- or subscription-history ID returned by a history operation. - `query`: none - `body`: none @@ -74,6 +80,7 @@ Purpose: Start AI-assisted reorganization for one transfer-history record. ### `transfer.history.redo_batch` `POST /api/v1/history/transfer/ai-redo`; policy effect: `external_side_effect`. Purpose: Start AI-assisted reorganization for an explicit list of transfer-history records. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: `history_ids` (array): Explicit persistent transfer-history IDs included in one batch redo request. @@ -81,13 +88,15 @@ Purpose: Start AI-assisted reorganization for an explicit list of transfer-histo ### `transfer.manual_history` `POST /api/v1/transfer/manual/history`; policy effect: `safe_read`. Purpose: Check whether supplied storage items already have successful transfer history. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none -- `body`: `episode_detail` (string|null): Episode mapping details used by manual transfer.; `episode_format` (string|null): Episode-number formatting rule used by manual transfer.; `episode_group` (string|null): TMDB episode-group identifier used for alternate episode ordering.; `episode_offset` (string|null): Integer offset added to detected episode numbers.; `episode_part` (string|null): Episode part number used when one episode is split across files.; `fileitem` (FileItem-Input): One complete source storage item returned by storage.list.; `fileitems` (array|null): Additional source storage items included in the same manual transfer.; `from_history` (boolean|null; default `False`): Treat the transfer input as originating from an existing history record.; `library_category_folder` (boolean|null): Create or use a category-level folder in the target library.; `library_type_folder` (boolean|null): Create or use a media-type folder in the target library.; `logid` (integer|null): One download-history or transfer-log identifier used by manual transfer.; `logids` (array|null): Multiple download-history or transfer-log identifiers included in manual transfer.; `media_id` (string|null): Source-native media ID. Always pair it with the exact media_source returned by search.; `media_source` (MediaSource|null): Metadata source identifier. Preserve the exact value returned with media_id.; `min_filesize` (integer|null; default `0`): Minimum source file size accepted by manual transfer, in bytes.; `music_release_regions` (array|null): Optional ISO 3166-1 release-region priority for music organization.; `music_release_scripts` (array|null): Optional ISO 15924 script priority for music organization.; `music_type` (string(recording,album)|null): Music identity level: recording, album, or artist where supported.; `musicbrainz_release_id` (string|null): Optional exact MusicBrainz Release UUID belonging to the selected album Release Group; never use it as media_id.; `preview` (boolean|null; default `False`): Validate and preview manual-transfer output without committing file changes.; `reorganize` (boolean|null; default `False`): Allow manual transfer to organize an item that was already processed.; `scrape` (boolean|null; default `False`): Generate metadata and images after manual transfer.; `season` (integer|null): Season number used by the media, search, subscription, or transfer operation.; `skip_success` (boolean; default `False`): Skip files already recorded as successfully organized.; `target_path` (string|null): Destination path used by manual transfer.; `target_storage` (string|null): Configured storage name receiving the manual transfer.; `transfer_type` (string|null): Manual-transfer mode, such as move, copy, link, or softlink.; `type_name` (string|null): Explicit media type name used when source IDs alone are ambiguous. +- `body`: `episode_detail` (string|null): Episode mapping details used by manual transfer.; `episode_format` (string|null): Episode-number formatting rule used by manual transfer.; `episode_group` (string|null): TMDB episode-group identifier used for alternate episode ordering.; `episode_offset` (string|null): Integer offset added to detected episode numbers.; `episode_part` (string|null): Episode part number used when one episode is split across files.; `fileitem` (FileItem-Input): One complete source storage item returned by storage.list.; `fileitems` (array|null): Additional source storage items included in the same manual transfer.; `from_history` (boolean|null; default `False`): Treat the transfer input as originating from an existing history record.; `library_category_folder` (boolean|null): Create or use a category-level folder in the target library.; `library_type_folder` (boolean|null): Create or use a media-type folder in the target library.; `logid` (integer|null): One download-history or transfer-log identifier used by manual transfer.; `logids` (array|null): Multiple download-history or transfer-log identifiers included in manual transfer.; `media_id` (string|null): Source-native media ID. Always pair it with the exact media_source returned by search.; `media_source` (MediaSource|null): Metadata source identifier. Preserve the exact value returned with media_id.; `min_filesize` (integer|null; default `0`): Minimum source file size accepted by manual transfer, in bytes.; `music_cue_enable` (boolean|null): Optional per-subscription CUE organization setting; omit to inherit the system default.; `music_release_regions` (array|null): Optional ISO 3166-1 release-region priority for music organization.; `music_release_scripts` (array|null): Optional ISO 15924 script priority for music organization.; `music_type` (string(recording,album)|null): Music identity level: recording, album, or artist where supported.; `musicbrainz_release_id` (string|null): Optional exact MusicBrainz Release UUID belonging to the selected album Release Group; never use it as media_id.; `preview` (boolean|null; default `False`): Validate and preview manual-transfer output without committing file changes.; `reorganize` (boolean|null; default `False`): Allow manual transfer to organize an item that was already processed.; `scrape` (boolean|null; default `False`): Generate metadata and images after manual transfer.; `season` (integer|null): Season number used by the media, search, subscription, or transfer operation.; `skip_success` (boolean; default `False`): Skip files already recorded as successfully organized.; `target_path` (string|null): Destination path used by manual transfer.; `target_storage` (string|null): Configured storage name receiving the manual transfer.; `transfer_type` (string|null): Manual-transfer mode, such as move, copy, link, or softlink.; `type_name` (string|null): Explicit media type name used when source IDs alone are ambiguous. ### `transfer.manual_review` `GET /api/v1/transfer/tasks/{task_id}/manual-review`; policy effect: `safe_read`. Purpose: Read one durable transfer task awaiting manual review. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `task_id*` (string): Stable durable transfer task ID returned by transfer.manual_reviews. - `query`: none - `body`: none @@ -95,6 +104,7 @@ Purpose: Read one durable transfer task awaiting manual review. ### `transfer.manual_review.resolve` `POST /api/v1/transfer/tasks/{task_id}/manual-review`; policy effect: `reversible_write`. Purpose: Record the authorized decision for one durable transfer manual-review operation. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `task_id*` (string): Stable durable transfer task ID returned by transfer.manual_reviews. - `query`: none - `body`: `decision*` (string(not_applied,applied)): Manual-review decision selected from the endpoint's declared enum.; `operation_id*` (string; minimum length `1`): Exact allowlisted MoviePilot operation ID selecting this oneOf branch.; `reason` (string; default ``): Human-readable justification recorded with a manual-review decision.; `result_payload` (object|null): Structured external-operation result recorded with manual review. @@ -102,6 +112,7 @@ Purpose: Record the authorized decision for one durable transfer manual-review o ### `transfer.manual_reviews` `GET /api/v1/transfer/tasks/manual-reviews`; policy effect: `safe_read`. Purpose: Page durable transfer tasks awaiting manual review or retry recovery. +Identity scope: `admin`; required role: `system_admin`. - `response`: structured page object; items stay in `data.items` and the exact total stays in `data.total`. - `path_params`: none - `query`: `page` (integer; default `1`; minimum `1`): One-based result page number.; `page_size` (integer; default `30`; minimum `1`; maximum `100`): Maximum records returned on one page.; `state` (string(manual_review,retry_wait); default `manual_review`): Current site, subscription, marketplace, or transfer state filter. @@ -110,6 +121,7 @@ Purpose: Page durable transfer tasks awaiting manual review or retry recovery. ### `transfer.name` `GET /api/v1/transfer/name`; policy effect: `safe_read`. Purpose: Preview the organized destination name for one source path and media identity. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: `filetype*` (string): Media file type used to preview the organized destination name.; `path*` (string): Storage or history path represented by this record. - `body`: none @@ -117,6 +129,7 @@ Purpose: Preview the organized destination name for one source path and media id ### `transfer.queue` `GET /api/v1/transfer/queue`; policy effect: `safe_read`. Purpose: List items waiting in the file-transfer queue. +Identity scope: `bound_user`; required role: `user`. - `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated. - `path_params`: none - `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result. @@ -125,6 +138,7 @@ Purpose: List items waiting in the file-transfer queue. ### `transfer.queue.delete` `DELETE /api/v1/transfer/queue`; policy effect: `destructive_write`. Purpose: Remove one exact storage item from the file-transfer queue and stop its transfer. +Identity scope: `bound_user`; required role: `user`. - `path_params`: none - `query`: none - `body`: `basename` (string|null): Base filename without its parent path.; `children` (array|null): Child storage items nested below this item.; `drive_id` (string|null): Provider-native storage drive identifier.; `extension` (string|null): Filename extension, including or excluding the leading dot as returned by storage.; `fileid` (string|null): Provider-native storage item identifier.; `modify_time` (number|null): Storage item modification timestamp.; `name` (string|null): Human-readable name of the site, storage item, subscription, or rule group.; `parent_fileid` (string|null): Provider-native identifier of the parent storage directory.; `path` (string|null; default `/`): Storage or history path represented by this record.; `pickcode` (string|null): 115 storage pickcode associated with the item.; `size` (integer|null): File or torrent size in bytes.; `storage` (string|null; default `local`): Configured storage name or storage type used by the operation.; `thumbnail` (string|null): Thumbnail URL returned by the storage provider.; `type` (string|null): MoviePilot media or storage item type required by the selected operation.; `url` (string|null): Site, storage, or torrent URL represented by this field. @@ -132,9 +146,10 @@ Purpose: Remove one exact storage item from the file-transfer queue and stop its ### `transfer.target_path` `POST /api/v1/transfer/manual/target-path`; policy effect: `safe_read`. Purpose: Resolve the configured transfer destination for supplied source storage items. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none -- `body`: `episode_detail` (string|null): Episode mapping details used by manual transfer.; `episode_format` (string|null): Episode-number formatting rule used by manual transfer.; `episode_group` (string|null): TMDB episode-group identifier used for alternate episode ordering.; `episode_offset` (string|null): Integer offset added to detected episode numbers.; `episode_part` (string|null): Episode part number used when one episode is split across files.; `fileitem` (FileItem-Input): One complete source storage item returned by storage.list.; `fileitems` (array|null): Additional source storage items included in the same manual transfer.; `from_history` (boolean|null; default `False`): Treat the transfer input as originating from an existing history record.; `library_category_folder` (boolean|null): Create or use a category-level folder in the target library.; `library_type_folder` (boolean|null): Create or use a media-type folder in the target library.; `logid` (integer|null): One download-history or transfer-log identifier used by manual transfer.; `logids` (array|null): Multiple download-history or transfer-log identifiers included in manual transfer.; `media_id` (string|null): Source-native media ID. Always pair it with the exact media_source returned by search.; `media_source` (MediaSource|null): Metadata source identifier. Preserve the exact value returned with media_id.; `min_filesize` (integer|null; default `0`): Minimum source file size accepted by manual transfer, in bytes.; `music_release_regions` (array|null): Optional ISO 3166-1 release-region priority for music organization.; `music_release_scripts` (array|null): Optional ISO 15924 script priority for music organization.; `music_type` (string(recording,album)|null): Music identity level: recording, album, or artist where supported.; `musicbrainz_release_id` (string|null): Optional exact MusicBrainz Release UUID belonging to the selected album Release Group; never use it as media_id.; `preview` (boolean|null; default `False`): Validate and preview manual-transfer output without committing file changes.; `reorganize` (boolean|null; default `False`): Allow manual transfer to organize an item that was already processed.; `scrape` (boolean|null; default `False`): Generate metadata and images after manual transfer.; `season` (integer|null): Season number used by the media, search, subscription, or transfer operation.; `skip_success` (boolean; default `False`): Skip files already recorded as successfully organized.; `target_path` (string|null): Destination path used by manual transfer.; `target_storage` (string|null): Configured storage name receiving the manual transfer.; `transfer_type` (string|null): Manual-transfer mode, such as move, copy, link, or softlink.; `type_name` (string|null): Explicit media type name used when source IDs alone are ambiguous. +- `body`: `episode_detail` (string|null): Episode mapping details used by manual transfer.; `episode_format` (string|null): Episode-number formatting rule used by manual transfer.; `episode_group` (string|null): TMDB episode-group identifier used for alternate episode ordering.; `episode_offset` (string|null): Integer offset added to detected episode numbers.; `episode_part` (string|null): Episode part number used when one episode is split across files.; `fileitem` (FileItem-Input): One complete source storage item returned by storage.list.; `fileitems` (array|null): Additional source storage items included in the same manual transfer.; `from_history` (boolean|null; default `False`): Treat the transfer input as originating from an existing history record.; `library_category_folder` (boolean|null): Create or use a category-level folder in the target library.; `library_type_folder` (boolean|null): Create or use a media-type folder in the target library.; `logid` (integer|null): One download-history or transfer-log identifier used by manual transfer.; `logids` (array|null): Multiple download-history or transfer-log identifiers included in manual transfer.; `media_id` (string|null): Source-native media ID. Always pair it with the exact media_source returned by search.; `media_source` (MediaSource|null): Metadata source identifier. Preserve the exact value returned with media_id.; `min_filesize` (integer|null; default `0`): Minimum source file size accepted by manual transfer, in bytes.; `music_cue_enable` (boolean|null): Optional per-subscription CUE organization setting; omit to inherit the system default.; `music_release_regions` (array|null): Optional ISO 3166-1 release-region priority for music organization.; `music_release_scripts` (array|null): Optional ISO 15924 script priority for music organization.; `music_type` (string(recording,album)|null): Music identity level: recording, album, or artist where supported.; `musicbrainz_release_id` (string|null): Optional exact MusicBrainz Release UUID belonging to the selected album Release Group; never use it as media_id.; `preview` (boolean|null; default `False`): Validate and preview manual-transfer output without committing file changes.; `reorganize` (boolean|null; default `False`): Allow manual transfer to organize an item that was already processed.; `scrape` (boolean|null; default `False`): Generate metadata and images after manual transfer.; `season` (integer|null): Season number used by the media, search, subscription, or transfer operation.; `skip_success` (boolean; default `False`): Skip files already recorded as successfully organized.; `target_path` (string|null): Destination path used by manual transfer.; `target_storage` (string|null): Configured storage name receiving the manual transfer.; `transfer_type` (string|null): Manual-transfer mode, such as move, copy, link, or softlink.; `type_name` (string|null): Explicit media type name used when source IDs alone are ambiguous. ## Body Models diff --git a/skills/moviepilot-api/api/workflow.md b/skills/moviepilot-api/api/workflow.md index 8c2745bfd..ab552a8c2 100644 --- a/skills/moviepilot-api/api/workflow.md +++ b/skills/moviepilot-api/api/workflow.md @@ -7,6 +7,7 @@ Workflow definitions, actions, event types, execution, sharing, and lifecycle co ### `workflow.actions` `GET /api/v1/workflow/actions`; policy effect: `safe_read`. Purpose: List built-in workflow action definitions and their parameter contracts. +Identity scope: `admin`; required role: `system_admin`. - `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated. - `path_params`: none - `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result. @@ -15,6 +16,7 @@ Purpose: List built-in workflow action definitions and their parameter contracts ### `workflow.create` `POST /api/v1/workflow/`; policy effect: `reversible_write`. Purpose: Create one workflow from a complete workflow definition. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: `actions` (array|null): Ordered workflow action definitions executed by this workflow or flow.; `add_time` (string|null): Timestamp when the workflow definition was created.; `current_action` (string|null): Identifier of the workflow action currently selected or executing.; `description` (string|null): Human-readable media, torrent, or subscription description.; `event_conditions` (object|null): Additional workflow event-filter conditions.; `event_type` (string|null): Exact event type returned by workflow.event_types.; `execution_config` (WorkflowExecutionConfig|null): Workflow runtime limits, concurrency, and failure-policy configuration.; `execution_state` (WorkflowExecutionState-Input|null): Persisted resumable workflow execution state.; `flows` (array|null): Workflow connection definitions linking action nodes.; `id` (integer|null): Persistent database identifier of the supplied record.; `last_time` (string|null): Timestamp of the workflow's most recent execution.; `name` (string|null): Human-readable name of the site, storage item, subscription, or rule group.; `result` (string|null): Persisted workflow action result value.; `run_count` (integer|null; default `0`): Number of times the workflow has been executed.; `state` (string|null): Current site, subscription, marketplace, or transfer state filter.; `timer` (string|null): Workflow timer or cron expression used for scheduled execution.; `trigger_type` (string|null; default `timer`): Workflow trigger filter: timer, event, manual, or all. @@ -22,6 +24,7 @@ Purpose: Create one workflow from a complete workflow definition. ### `workflow.delete` `DELETE /api/v1/workflow/{workflow_id}`; policy effect: `destructive_write`. Purpose: Delete one configured workflow by persistent workflow ID. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `workflow_id*` (integer): Persistent workflow ID returned by workflow.list. - `query`: none - `body`: none @@ -29,6 +32,7 @@ Purpose: Delete one configured workflow by persistent workflow ID. ### `workflow.event_types` `GET /api/v1/workflow/event_types`; policy effect: `safe_read`. Purpose: List event types that can trigger workflows. +Identity scope: `admin`; required role: `system_admin`. - `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated. - `path_params`: none - `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result. @@ -37,6 +41,7 @@ Purpose: List event types that can trigger workflows. ### `workflow.fork` `POST /api/v1/workflow/fork`; policy effect: `external_side_effect`. Purpose: Create a local workflow from one shared workflow definition. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: `actions` (string|null): Ordered workflow action definitions executed by this workflow or flow.; `context` (string|null): Persisted workflow execution context available to later actions.; `count` (integer|null; default `0`): Maximum number of records to return on the requested page.; `date` (string|null): Record creation or completion timestamp used by the history item.; `description` (string|null): Human-readable media, torrent, or subscription description.; `event_conditions` (string|null): Additional workflow event-filter conditions.; `event_type` (string|null): Exact event type returned by workflow.event_types.; `flows` (string|null): Workflow connection definitions linking action nodes.; `id` (integer|null): Persistent database identifier of the supplied record.; `name` (string|null): Human-readable name of the site, storage item, subscription, or rule group.; `share_comment` (string|null): Optional explanatory comment published with a shared item.; `share_title` (string|null): Public title used when publishing a subscription or workflow.; `share_uid` (string|null): Exact MoviePilot Server sharing-user ID to follow or unfollow.; `share_user` (string|null): Public contributor name used when publishing a subscription or workflow.; `timer` (string|null): Workflow timer or cron expression used for scheduled execution.; `trigger_type` (string|null): Workflow trigger filter: timer, event, manual, or all. @@ -44,6 +49,7 @@ Purpose: Create a local workflow from one shared workflow definition. ### `workflow.get` `GET /api/v1/workflow/{workflow_id}`; policy effect: `safe_read`. Purpose: Read one complete configured workflow definition. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `workflow_id*` (integer): Persistent workflow ID returned by workflow.list. - `query`: none - `body`: none @@ -51,6 +57,7 @@ Purpose: Read one complete configured workflow definition. ### `workflow.list` `GET /api/v1/workflow/agent`; policy effect: `safe_read`. Purpose: List configured workflows and their execution state. +Identity scope: `bound_user`; required role: `user`. - `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated. - `path_params`: none - `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `name` (string|null): Human-readable name of the site, storage item, subscription, or rule group.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.; `state` (string(W,R,P,S,F,all); default `all`): Current site, subscription, marketplace, or transfer state filter.; `trigger_type` (string(timer,event,manual,all); default `all`): Workflow trigger filter: timer, event, manual, or all. @@ -59,6 +66,7 @@ Purpose: List configured workflows and their execution state. ### `workflow.pause` `POST /api/v1/workflow/{workflow_id}/pause`; policy effect: `reversible_write`. Purpose: Disable automatic execution of one configured workflow. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `workflow_id*` (integer): Persistent workflow ID returned by workflow.list. - `query`: none - `body`: none @@ -66,6 +74,7 @@ Purpose: Disable automatic execution of one configured workflow. ### `workflow.plugin.actions` `GET /api/v1/workflow/plugin/actions`; policy effect: `safe_read`. Purpose: List workflow actions contributed by installed plugins, optionally filtered by plugin ID. +Identity scope: `admin`; required role: `system_admin`. - `response`: `data` remains a list; omitting both `page` and `count` keeps the complete legacy result. `collection.result_count` reports the returned items and `collection.total_count` reports the exact pre-pagination total. For counts or summaries, send `page=1,count=1`, read `collection.total_count`, and do not fall back to a database query because the item preview was truncated. - `path_params`: none - `query`: `count` (integer|null): Optional page size for a legacy full-list endpoint. Supplying page or count activates pagination; an omitted count then uses 50.; `page` (integer|null): Optional one-based page for a legacy full-list endpoint. Omit both page and count to keep the original unpaginated full result.; `plugin_id` (string): Exact installed or marketplace plugin ID. @@ -74,6 +83,7 @@ Purpose: List workflow actions contributed by installed plugins, optionally filt ### `workflow.reset` `POST /api/v1/workflow/{workflow_id}/reset`; policy effect: `reversible_write`. Purpose: Reset one configured workflow definition and execution state. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `workflow_id*` (integer): Persistent workflow ID returned by workflow.list. - `query`: none - `body`: none @@ -81,6 +91,7 @@ Purpose: Reset one configured workflow definition and execution state. ### `workflow.run` `POST /api/v1/workflow/{workflow_id}/run`; policy effect: `external_side_effect`. Purpose: Run one configured workflow from the beginning or resume point. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `workflow_id*` (integer): Persistent workflow ID returned by workflow.list. - `query`: `from_begin` (boolean|null; default `True`): Restart the workflow from its first action instead of resuming progress. - `body`: none @@ -88,6 +99,7 @@ Purpose: Run one configured workflow from the beginning or resume point. ### `workflow.share` `POST /api/v1/workflow/share`; policy effect: `external_side_effect`. Purpose: Publish one configured workflow to the MoviePilot sharing service. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: none - `query`: none - `body`: `actions` (string|null): Ordered workflow action definitions executed by this workflow or flow.; `context` (string|null): Persisted workflow execution context available to later actions.; `count` (integer|null; default `0`): Maximum number of records to return on the requested page.; `date` (string|null): Record creation or completion timestamp used by the history item.; `description` (string|null): Human-readable media, torrent, or subscription description.; `event_conditions` (string|null): Additional workflow event-filter conditions.; `event_type` (string|null): Exact event type returned by workflow.event_types.; `flows` (string|null): Workflow connection definitions linking action nodes.; `id` (integer|null): Persistent database identifier of the supplied record.; `name` (string|null): Human-readable name of the site, storage item, subscription, or rule group.; `share_comment` (string|null): Optional explanatory comment published with a shared item.; `share_title` (string|null): Public title used when publishing a subscription or workflow.; `share_uid` (string|null): Exact MoviePilot Server sharing-user ID to follow or unfollow.; `share_user` (string|null): Public contributor name used when publishing a subscription or workflow.; `timer` (string|null): Workflow timer or cron expression used for scheduled execution.; `trigger_type` (string|null): Workflow trigger filter: timer, event, manual, or all. @@ -95,6 +107,7 @@ Purpose: Publish one configured workflow to the MoviePilot sharing service. ### `workflow.share.delete` `DELETE /api/v1/workflow/share/{share_id}`; policy effect: `external_side_effect`. Purpose: Delete one shared-workflow publication by share ID. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `share_id*` (integer): Persistent MoviePilot Server share ID returned by a share-list operation. - `query`: none - `body`: none @@ -102,6 +115,7 @@ Purpose: Delete one shared-workflow publication by share ID. ### `workflow.shares` `GET /api/v1/workflow/shares`; policy effect: `safe_read`. Purpose: List shared workflows with name and pagination filters. +Identity scope: `admin`; required role: `system_admin`. - `response`: `data` remains a list and `collection.result_count` reports the returned items. `collection.total_count` is omitted because this endpoint or its upstream source does not expose a total. - `path_params`: none - `query`: `count` (integer|null; default `30`): Maximum number of records to return on the requested page.; `name` (string|null): Human-readable name of the site, storage item, subscription, or rule group.; `page` (integer|null; default `1`): One-based result page number. @@ -110,6 +124,7 @@ Purpose: List shared workflows with name and pagination filters. ### `workflow.start` `POST /api/v1/workflow/{workflow_id}/start`; policy effect: `reversible_write`. Purpose: Enable automatic execution of one configured workflow. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `workflow_id*` (integer): Persistent workflow ID returned by workflow.list. - `query`: none - `body`: none @@ -117,6 +132,7 @@ Purpose: Enable automatic execution of one configured workflow. ### `workflow.update` `PUT /api/v1/workflow/{workflow_id}`; policy effect: `reversible_write`. Purpose: Replace one configured workflow definition. +Identity scope: `admin`; required role: `system_admin`. - `path_params`: `workflow_id*` (integer): Persistent workflow ID returned by workflow.list. - `query`: none - `body`: `actions` (array|null): Ordered workflow action definitions executed by this workflow or flow.; `add_time` (string|null): Timestamp when the workflow definition was created.; `current_action` (string|null): Identifier of the workflow action currently selected or executing.; `description` (string|null): Human-readable media, torrent, or subscription description.; `event_conditions` (object|null): Additional workflow event-filter conditions.; `event_type` (string|null): Exact event type returned by workflow.event_types.; `execution_config` (WorkflowExecutionConfig|null): Workflow runtime limits, concurrency, and failure-policy configuration.; `execution_state` (WorkflowExecutionState-Input|null): Persisted resumable workflow execution state.; `flows` (array|null): Workflow connection definitions linking action nodes.; `id` (integer|null): Persistent database identifier of the supplied record.; `last_time` (string|null): Timestamp of the workflow's most recent execution.; `name` (string|null): Human-readable name of the site, storage item, subscription, or rule group.; `result` (string|null): Persisted workflow action result value.; `run_count` (integer|null; default `0`): Number of times the workflow has been executed.; `state` (string|null): Current site, subscription, marketplace, or transfer state filter.; `timer` (string|null): Workflow timer or cron expression used for scheduled execution.; `trigger_type` (string|null; default `timer`): Workflow trigger filter: timer, event, manual, or all. diff --git a/tests/test_agent_api_identity.py b/tests/test_agent_api_identity.py new file mode 100644 index 000000000..967210459 --- /dev/null +++ b/tests/test_agent_api_identity.py @@ -0,0 +1,320 @@ +"""Agent 公共资料访问、真实身份解析和授权范围的离线回归。""" + +import asyncio +import json +import re +from dataclasses import replace +from types import SimpleNamespace +from unittest.mock import AsyncMock, Mock, patch + +import pytest + +from app.agent.api.executor import ApiExecutionContext, ApiExecutionError, MoviePilotApiExecutor +from app.agent.policy.api import ( + API_OPERATION_ROUTES, + API_OPERATION_SPECS, + API_UNBOUND_READ_OPERATIONS, + ApiIdentityScope, + resolve_api_operation, +) +from app.agent.policy.contracts import ActionEffect, ConfirmationMode, PrincipalRole, ResultSensitivity +from app.agent.tools.impl.api import MoviePilotApiTool +from app.db.adapters.user import TransactionalUserRepository +from app.db.models.user import User +from app.db.session import SessionFactory, async_session_scope +from app.schemas.types import NotificationChannel + +_MUSIC_ID = "00000000-0000-0000-0000-000000000001" +# 参数独立于生产注册表:每项都经真实输入校验和 HTTP 执行器,不以 safe_read 推断公共性。 +PUBLIC_CASES = { + "media.search": {"query": {"title": "示例电影"}}, + "media.person.search": {"query": {"title": "示例人物", "type": "person"}}, + "media.person.credits": {"path_params": {"source": "tmdb", "person_id": 1}}, + "media.episode_schedule": {"path_params": {"tmdbid": 1, "season": 1}}, + "media.detail": {"path_params": {"media_id": "1"}, "query": {"media_source": "tmdb", "type_name": "电影"}}, + "media.episode_groups": {"path_params": {"tmdbid": 1}}, + "media.episode_group.seasons": {"path_params": {"episode_group": "group"}}, + "media.seasons": {}, + "music.recognize": {"body": {"media_source": "musicbrainz", "media_id": _MUSIC_ID}}, + "music.explore": {}, + "music.album.get": {"path_params": {"album_id": _MUSIC_ID}}, + "music.album.related": {"path_params": {"album_id": _MUSIC_ID}}, + "music.artist.get": {"path_params": {"artist_id": _MUSIC_ID}}, + "music.artist.albums": {"path_params": {"artist_id": _MUSIC_ID}}, + "music.artist.related": {"path_params": {"artist_id": _MUSIC_ID}}, + "recommendation.list": {}, + "subscription.shares": {}, + "subscription.popular": {"query": {"stype": "movie"}}, + "subscription.share.statistics": {}, + "plugin.statistics": {}, +} + + +@pytest.mark.parametrize("operation_id,arguments", PUBLIC_CASES.items()) +@pytest.mark.parametrize("channel", [NotificationChannel.WechatClawBot, NotificationChannel.Telegram]) +@pytest.mark.parametrize("is_admin", [False, True]) +@pytest.mark.parametrize("user_state", ["unbound", "inactive", "active"]) +def test_public_queries_do_not_require_channel_binding(operation_id, arguments, channel, is_admin, user_state): + """所有公共操作通过真实宿主认证执行,绑定缺失或停用均不阻断公共结果。""" + user = None if user_state == "unbound" else SimpleNamespace(is_active=user_state == "active") + users = SimpleNamespace(async_get_by_name=AsyncMock(return_value=user), find_name_by_bindings=Mock()) + gateway = MoviePilotApiTool(session_id="session", user_id="channel-user", data=SimpleNamespace(users=users)) + gateway.set_message_attr(channel=channel.value, source="main-bot", username="admin") + gateway.set_agent_context({"is_admin": is_admin}) + response = SimpleNamespace( + status_code=200, headers={}, aclose=AsyncMock(), + json=lambda: {"success": True, "data": [{"title": "公共资料"}]}, + ) + with ( + patch("app.application.security.auth.build_superuser_token_payload", return_value=SimpleNamespace( + sub=7, username="admin", super_user=True, + )), + patch("app.agent.api.executor.create_access_token", return_value="host-token") as token, + patch("app.agent.api.executor.AsyncRequestUtils.request", new=AsyncMock(return_value=response)) as request, + ): + result = asyncio.run(gateway.run(operation_id, **arguments)) + + assert json.loads(result) == {"success": True, "data": [{"title": "公共资料"}]} + request.assert_awaited_once() + assert request.call_args.kwargs["method"] == API_OPERATION_ROUTES[operation_id].method + assert "/api/v1/" in request.call_args.kwargs["url"] + token.assert_called_once_with(userid=7, username="admin", super_user=True) + users.async_get_by_name.assert_not_awaited() + users.find_name_by_bindings.assert_not_called() + response.aclose.assert_awaited_once() + + +@pytest.mark.parametrize("operation_id,arguments", [ + ("subscription.list", {}), + ("download.history.list", {}), + ("subscription.add", {"body": {"name": "测试电影", "type": "movie"}}), + ("library.latest", {"query": {"server": "server"}}), + ("media.recognize", {"query": {"title": "测试电影"}}), + ("media.recognize_file", {"query": {"path": "/media/file.mkv"}}), + ("search.results", {}), + ("storage.list", {"body": {"path": "/", "storage": "local", "type": "dir"}}), + ("config.user.get", {}), + ("config.public.get", {"path_params": {"key": "Storages"}}), + ("subscription.follow.list", {}), +]) +@pytest.mark.parametrize("user_state", ["unbound", "inactive"]) +def test_instance_and_user_operations_still_require_binding(operation_id, arguments, user_state): + """个人和实例数据不能借用公共查询身份,渠道管理员也保留绑定要求。""" + user = None if user_state == "unbound" else SimpleNamespace(is_active=False) + users = SimpleNamespace( + async_get_by_name=AsyncMock(return_value=user), + find_name_by_bindings=Mock(return_value="disabled" if user else None), + ) + gateway = MoviePilotApiTool(session_id="session", user_id="channel-user", data=SimpleNamespace(users=users)) + gateway.set_message_attr(channel=NotificationChannel.WechatClawBot.value, source="main-bot", username="admin") + gateway.set_agent_context({"is_admin": True}) + with ( + patch("app.application.security.auth.build_superuser_token_payload") as identity, + patch("app.agent.api.executor.AsyncRequestUtils.request", new=AsyncMock()) as request, + ): + result = asyncio.run(gateway.run(operation_id, **arguments)) + assert json.loads(result)["error"] == "operation_unavailable" + identity.assert_not_called() + request.assert_not_awaited() + + +@pytest.mark.parametrize("operation_id", sorted(set(API_OPERATION_ROUTES) - set(PUBLIC_CASES))) +def test_public_executor_rejects_every_nonpublic_operation(operation_id): + """持有宿主身份的公共执行器在发送前拒绝其余全部个人、实例和管理员操作。""" + executor = MoviePilotApiExecutor(context=ApiExecutionContext( + user_id="7", username="admin", is_admin=True, public_read_only=True, + )) + with patch("app.agent.api.executor.AsyncRequestUtils.request", new=AsyncMock()) as request: + with pytest.raises(ApiExecutionError, match="公共查询执行器"): + asyncio.run(executor.execute(operation_id)) + request.assert_not_awaited() + + +def test_public_allowlist_only_contains_audited_metadata_queries(): + """公共清单独立锁定,写入、私密结果和管理数据不能因只读标签被自动放行。""" + assert API_UNBOUND_READ_OPERATIONS == set(PUBLIC_CASES) + for operation_id in PUBLIC_CASES: + spec = resolve_api_operation(operation_id) + assert spec.identity_scope is ApiIdentityScope.PUBLIC + assert spec.effect is ActionEffect.SAFE_READ + assert spec.required_role is PrincipalRole.USER + assert spec.result_sensitivity is ResultSensitivity.NORMAL + assert spec.confirmation is ConfirmationMode.NONE + + +@pytest.mark.parametrize("changes", [ + {"effect": ActionEffect.REVERSIBLE_WRITE}, + {"effect": ActionEffect.SENSITIVE_READ}, + {"required_role": PrincipalRole.SYSTEM_ADMIN}, + {"result_sensitivity": ResultSensitivity.PRIVATE}, + {"confirmation": ConfirmationMode.REQUIRED}, +]) +def test_public_identity_cannot_be_declared_for_privileged_operations(changes): + """错误公共身份声明应在注册时失败,不能带着管理员或私密能力进入运行时。""" + with pytest.raises(ValueError, match="公共 API 身份"): + replace(resolve_api_operation("recommendation.list"), **changes) + + +@pytest.mark.parametrize("channel", [NotificationChannel.Telegram, NotificationChannel.WechatClawBot]) +@pytest.mark.parametrize("bound", [False, True]) +def test_channel_nickname_cannot_impersonate_moviepilot_user(db, channel, bound): + """渠道展示名与超级管理员同名时,仍只能使用稳定发送者 ID 的实际绑定。""" + db.add(User(name="admin", is_active=True, is_superuser=True)) + user = db.add(User( + name="alice", is_active=True, is_superuser=False, + settings={f"{channel.name.lower()}_userid": "sender"} if bound else {}, + )) + users = TransactionalUserRepository(sync_session=SessionFactory, async_session=async_session_scope) + gateway = MoviePilotApiTool(session_id="session", user_id="sender", data=SimpleNamespace(users=users)) + gateway.set_message_attr(channel=channel.value, source="bot", username="admin") + if bound: + assert asyncio.run(gateway._resolve_api_identity()) == (str(user.id), "alice", False) + else: + with pytest.raises(ApiExecutionError, match="未绑定"): + asyncio.run(gateway._resolve_api_identity()) + + +@pytest.mark.parametrize("channel", [NotificationChannel.Web, NotificationChannel.WebAgent]) +def test_web_identity_uses_authenticated_id_instead_of_stale_name(db, channel): + """旧会话用户名与另一个账号同名时,不能替换当前已登录账号身份。""" + db.add(User(name="old-name", is_active=True, is_superuser=True)) + user = db.add(User(name="new-name", is_active=True, is_superuser=False)) + users = TransactionalUserRepository(sync_session=SessionFactory, async_session=async_session_scope) + gateway = MoviePilotApiTool(session_id="session", user_id=str(user.id), data=SimpleNamespace(users=users)) + gateway.set_message_attr(channel=channel.value, source="web", username="old-name") + assert asyncio.run(gateway._resolve_api_identity()) == (str(user.id), "new-name", False) + + +@pytest.mark.parametrize("state", ["inactive", "missing"]) +def test_web_identity_cannot_fall_back_after_account_disappears(db, state): + """停用或删除的 Web 用户不能依靠数字 ID 回退继续访问个人数据。""" + user = db.add(User(name="disabled", is_active=False, is_superuser=False)) + users = TransactionalUserRepository(sync_session=SessionFactory, async_session=async_session_scope) + user_id = str(user.id) if state == "inactive" else "999999" + gateway = MoviePilotApiTool(session_id="session", user_id=user_id, data=SimpleNamespace(users=users)) + gateway.set_message_attr(channel=NotificationChannel.WebAgent.value, source="web", username=user.name) + with pytest.raises(ApiExecutionError, match="已停用" if state == "inactive" else "重新登录"): + asyncio.run(gateway._resolve_api_identity()) + + +@pytest.mark.parametrize("source", ["openai", "openai.responses", "anthropic"]) +@pytest.mark.parametrize("is_admin", [False, True]) +def test_api_token_integration_uses_host_identity_only_after_authentication(source, is_admin): + """兼容入口的会话 ID 不能当用户 ID,且仅可信管理员上下文可使用宿主身份。""" + users = SimpleNamespace(async_get_by_id=AsyncMock(), async_get_by_name=AsyncMock()) + gateway = MoviePilotApiTool(session_id="session", user_id="client-session", data=SimpleNamespace(users=users)) + gateway.set_message_attr(channel=NotificationChannel.Web.value, source=source, username="admin") + gateway.set_agent_context({"is_admin": is_admin}) + with patch("app.application.security.auth.build_superuser_token_payload", return_value=SimpleNamespace( + sub=7, username="admin", super_user=True, + )) as identity: + if is_admin: + assert asyncio.run(gateway._resolve_api_identity()) == ("7", "admin", True) + identity.assert_called_once() + else: + with pytest.raises(ApiExecutionError, match="重新登录"): + asyncio.run(gateway._resolve_api_identity()) + identity.assert_not_called() + users.async_get_by_id.assert_not_awaited() + users.async_get_by_name.assert_not_awaited() + + +@pytest.mark.parametrize("channel,label", [ + (NotificationChannel.WechatClawBot, "微信 ClawBot 用户"), + (NotificationChannel.Wechat, "企业微信用户"), + (NotificationChannel.Telegram, "Telegram用户"), + (NotificationChannel.Discord, "Discord用户"), + (NotificationChannel.Feishu, "飞书用户"), + (NotificationChannel.QQ, "QQ 用户 ID 或 QQ 用户 OpenID"), + (NotificationChannel.Slack, "Slack用户"), + (NotificationChannel.VoceChat, "VoceChat用户"), + (NotificationChannel.SynologyChat, "SynologyChat用户"), +]) +def test_binding_failure_contains_correct_profile_field_and_sender_id(channel, label): + """各通知渠道给出实际前端字段、当前发送者 ID 和唯一启用绑定要求。""" + users = SimpleNamespace(find_name_by_bindings=Mock(return_value=None)) + gateway = MoviePilotApiTool(session_id="session", user_id="actual-sender", data=SimpleNamespace(users=users)) + gateway.set_message_attr(channel=channel.value, source="bot", username="display-name") + result = json.loads(asyncio.run(gateway.run("subscription.list"))) + assert result["error"] == "operation_unavailable" + assert "右上角头像 → 个人信息 → 账号绑定" in result["message"] + assert label in result["message"] + assert '"actual-sender"' in result["message"] + assert "保存并重试" in result["message"] + assert "重复绑定" in result["message"] + assert "不要填写昵称、群 ID 或机器人 ID" in result["message"] + assert "display-name" not in result["message"] + + +def test_background_task_keeps_host_persisted_identity(db): + """无渠道的可信后台任务仍按宿主持久化用户名执行,不误用渠道绑定规则。""" + user = db.add(User(name="task-owner", is_active=True, is_superuser=False)) + users = TransactionalUserRepository(sync_session=SessionFactory, async_session=async_session_scope) + gateway = MoviePilotApiTool(session_id="session", user_id="system", data=SimpleNamespace(users=users)) + gateway.set_message_attr(channel=None, source=None, username=user.name) + assert asyncio.run(gateway._resolve_api_identity()) == (str(user.id), user.name, False) + + +@pytest.mark.parametrize("operation_id,arguments", [ + ("plugin.ratings", {}), + ("plugin.rating", {"path_params": {"plugin_id": "Example"}}), + ("plugin.rating.submit", {"path_params": {"plugin_id": "Example"}, "body": {"rating": 4}}), +]) +@pytest.mark.parametrize("is_admin", [False, True]) +def test_plugin_rating_gateway_matches_rest_administrator_requirement(operation_id, arguments, is_admin): + """插件评分包含实例自己的记录,不能按普通公共统计放行。""" + user = SimpleNamespace(id=11, name="alice", is_active=True, is_superuser=False) + users = SimpleNamespace(find_name_by_bindings=Mock(return_value=user.name), async_get_by_name=AsyncMock(return_value=user)) + gateway = MoviePilotApiTool(session_id="session", user_id="sender", data=SimpleNamespace(users=users)) + gateway.set_message_attr(channel=NotificationChannel.Telegram.value, source="bot", username="admin") + gateway.set_agent_context({"is_admin": is_admin}) + with ( + patch("app.application.security.auth.build_superuser_token_payload", return_value=SimpleNamespace( + sub=7, username="admin", super_user=True, + )) as identity, + patch("app.agent.tools.impl.api.MoviePilotApiExecutor") as executor_type, + ): + executor_type.return_value.execute = AsyncMock(return_value='{"success": true}') + result = json.loads(asyncio.run(gateway.run(operation_id, **arguments))) + assert resolve_api_operation(operation_id).identity_scope is ApiIdentityScope.ADMIN + if is_admin: + assert result["success"] is True + identity.assert_called_once() + assert executor_type.call_args.kwargs["context"].public_read_only is False + else: + assert result["error"] == "permission_denied" + identity.assert_not_called() + executor_type.return_value.execute.assert_not_awaited() + + +def test_identity_scopes_cover_every_operation_and_match_rest_dependencies(): + """全部操作均有身份范围,公共端点不能依赖当前用户,超级用户端点不能声明普通角色。""" + from app.api.routers import API_V1_ROUTER_SPECS + + endpoints = {} + for router_spec in API_V1_ROUTER_SPECS: + for route in router_spec.router.routes: + if not hasattr(route, "dependant"): + continue + path = re.sub(r"\{[^}]+\}", "{}", "/api/v1" + router_spec.prefix + route.path) + for method in route.methods: + endpoints[method, path] = route + assert len(API_OPERATION_SPECS) == len(API_OPERATION_ROUTES) == 231 + for spec in API_OPERATION_SPECS: + assert isinstance(spec.identity_scope, ApiIdentityScope) + if spec.identity_scope is not ApiIdentityScope.PUBLIC: + assert spec.identity_scope is ( + ApiIdentityScope.ADMIN if spec.required_role is PrincipalRole.SYSTEM_ADMIN else ApiIdentityScope.BOUND_USER + ) + registered = API_OPERATION_ROUTES[spec.operation_id] + paths = [registered.path] + if "{source}" in registered.path: + paths = [registered.path.replace("{source}", source) for source in ("tmdb", "douban", "bangumi", "anilist")] + for path in paths: + endpoint = endpoints[registered.method, re.sub(r"\{[^}]+\}", "{}", path)] + dependencies = {dep.call.__name__: dep.name for dep in endpoint.dependant.dependencies} + if any("superuser" in name for name in dependencies): + assert spec.required_role is PrincipalRole.SYSTEM_ADMIN, spec.operation_id + if spec.identity_scope is ApiIdentityScope.PUBLIC: + assert dependencies.get("verify_token") == "_", spec.operation_id + assert not any("current_user" in name or "active_" in name for name in dependencies), spec.operation_id diff --git a/tests/test_builtin_skill_boundaries.py b/tests/test_builtin_skill_boundaries.py index d6286e930..7b3691653 100644 --- a/tests/test_builtin_skill_boundaries.py +++ b/tests/test_builtin_skill_boundaries.py @@ -117,7 +117,7 @@ def test_modified_builtin_skills_have_incremented_versions() -> None: "command-dispatch": "2", "database-operation": "9", "feedback-issue": "11", - "moviepilot-api": "50", + "moviepilot-api": "53", "moviepilot-update": "5", "organize-files": "5", "transfer-failed-retry": "5",