feat(agent): complete moviepilot api contracts

This commit is contained in:
jxxghp
2026-09-11 07:21:05 +08:00
parent dbd0d68f8f
commit c5d5698e04
13 changed files with 478 additions and 88 deletions

View File

@@ -119,6 +119,12 @@ API_FIRST_BATCH_OPERATION_SPECS: tuple[ApiOperationSpec, ...] = (
_spec("subscription.history"),
_write("subscription.delete", effect=ActionEffect.DESTRUCTIVE_WRITE, recovery=_DELETE_RECOVERABLE),
_spec("download.add", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, confirmation=_CONFIRM, recovery=_IDEMPOTENT),
_spec(
"download.artist_collection",
effect=ActionEffect.EXTERNAL_SIDE_EFFECT,
confirmation=_CONFIRM,
recovery=_IDEMPOTENT,
),
_spec("download.tasks.active"),
_spec("download.clients"),
_spec("download.paths"),
@@ -298,6 +304,9 @@ API_EXTENDED_OPERATION_SPECS: tuple[ApiOperationSpec, ...] = (
_admin_read("dashboard.network"),
_spec("media.sources"),
_spec("media.recognize_file"),
_admin_read("media.cache.get", sensitivity=ResultSensitivity.PRIVATE),
_write("media.cache.delete", effect=ActionEffect.DESTRUCTIVE_WRITE, recovery=RecoveryMode.NONE),
_write("media.cache.clear", effect=ActionEffect.DESTRUCTIVE_WRITE, recovery=RecoveryMode.NONE),
_spec("media.classification.fields"),
_spec("media.classification.policy.get"),
_admin_read("media.classification.policy.validate"),
@@ -395,6 +404,14 @@ API_EXTENDED_OPERATION_SPECS: tuple[ApiOperationSpec, ...] = (
),
_spec("subscription.user.list"),
_spec("subscription.files", result_sensitivity=ResultSensitivity.PRIVATE),
_spec("subscription.execution.list", result_sensitivity=ResultSensitivity.PRIVATE),
_spec("subscription.execution.get", result_sensitivity=ResultSensitivity.PRIVATE),
_user_write(
"subscription.execution.cancel",
effect=ActionEffect.EXTERNAL_SIDE_EFFECT,
recovery=RecoveryMode.RECONCILE,
sensitivity=ResultSensitivity.PRIVATE,
),
_user_write("subscription.share", effect=ActionEffect.EXTERNAL_SIDE_EFFECT, recovery=RecoveryMode.RECONCILE),
_user_write(
"subscription.share.delete",
@@ -588,6 +605,7 @@ API_OPERATION_ROUTES: dict[str, ApiOperationRoute] = {
"subscription.history": ApiOperationRoute("GET", "/api/v1/subscribe/history/{mtype}"),
"subscription.delete": ApiOperationRoute("DELETE", "/api/v1/subscribe/{subscribe_id}"),
"download.add": ApiOperationRoute("POST", "/api/v1/download/add"),
"download.artist_collection": ApiOperationRoute("POST", "/api/v1/download/artist-collection"),
"download.tasks.active": ApiOperationRoute("GET", "/api/v1/download/"),
"download.clients": ApiOperationRoute("GET", "/api/v1/download/clients"),
"download.paths": ApiOperationRoute("GET", "/api/v1/download/paths"),
@@ -658,6 +676,9 @@ API_OPERATION_ROUTES: dict[str, ApiOperationRoute] = {
"dashboard.network": ApiOperationRoute("GET", "/api/v1/dashboard/network"),
"media.sources": ApiOperationRoute("GET", "/api/v1/media/source"),
"media.recognize_file": ApiOperationRoute("GET", "/api/v1/media/recognize_file"),
"media.cache.get": ApiOperationRoute("GET", "/api/v1/tmdb/cache"),
"media.cache.delete": ApiOperationRoute("DELETE", "/api/v1/tmdb/cache/{cache_key}"),
"media.cache.clear": ApiOperationRoute("DELETE", "/api/v1/tmdb/cache"),
"media.classification.fields": ApiOperationRoute("GET", "/api/v1/media/classification/fields"),
"media.classification.policy.get": ApiOperationRoute("GET", "/api/v1/media/classification/policy"),
"media.classification.policy.update": ApiOperationRoute("PUT", "/api/v1/media/classification/policy"),
@@ -703,6 +724,13 @@ API_OPERATION_ROUTES: dict[str, ApiOperationRoute] = {
"subscription.history.delete": ApiOperationRoute("DELETE", "/api/v1/subscribe/history/{history_id}"),
"subscription.user.list": ApiOperationRoute("GET", "/api/v1/subscribe/user/{username}"),
"subscription.files": ApiOperationRoute("GET", "/api/v1/subscribe/files/{subscribe_id}"),
"subscription.execution.list": ApiOperationRoute("GET", "/api/v1/subscribe/execution/batches"),
"subscription.execution.get": ApiOperationRoute(
"GET", "/api/v1/subscribe/execution/batches/{batch_id}"
),
"subscription.execution.cancel": ApiOperationRoute(
"PUT", "/api/v1/subscribe/execution/batches/{batch_id}/cancel"
),
"subscription.share": ApiOperationRoute("POST", "/api/v1/subscribe/share"),
"subscription.share.delete": ApiOperationRoute("DELETE", "/api/v1/subscribe/share/{share_id}"),
"subscription.fork": ApiOperationRoute("POST", "/api/v1/subscribe/fork"),

View File

@@ -12,6 +12,7 @@ OPERATION_DESCRIPTIONS = {
"config.system.get": "Discover registered system settings or read one exact setting.",
"config.system.update": "Update one exact registered system setting.",
"download.add": "Submit one torrent to MoviePilot's normal download workflow.",
"download.artist_collection": "Submit one artist-wide music torrent to MoviePilot's collection download workflow.",
"download.clients": "List enabled downloader instance names and provider types without credentials.",
"download.history.list": "Page MoviePilot download-history records in reverse chronological order.",
"download.history.delete": "Delete one MoviePilot download-history record.",
@@ -29,6 +30,9 @@ OPERATION_DESCRIPTIONS = {
"library.exists": "Check configured media servers for one canonical media identity.",
"library.latest": "List recently added items from one configured media-server instance for the current user.",
"media.detail": "Read canonical media details from one selected metadata source.",
"media.cache.get": "Inspect the administrator-only TMDB recognition cache and summary counts.",
"media.cache.delete": "Delete one administrator-only TMDB recognition-cache entry by exact key.",
"media.cache.clear": "Clear the complete administrator-only TMDB recognition cache.",
"media.episode_schedule": "Read TMDB episode release information for one season.",
"media.person.credits": "Read one person's credits from the selected metadata source.",
"media.person.search": "Search people and music artists across selected metadata sources.",
@@ -154,6 +158,9 @@ OPERATION_DESCRIPTIONS = {
"storage.rename": "Rename one exact storage item, optionally applying media-aware recursive renaming.",
"subscription.delete_by_media": "Delete accessible subscriptions matching one canonical media identity.",
"subscription.files": "Read local library and transfer-file coverage for one accessible subscription.",
"subscription.execution.list": "List recent durable subscription-search batches visible to the current user.",
"subscription.execution.get": "Read one durable subscription-search batch visible to the current user.",
"subscription.execution.cancel": "Request cancellation of one durable subscription-search batch before its download side-effect boundary.",
"subscription.find": "Find one accessible subscription by canonical media identity and optional season.",
"subscription.follow.add": "Follow one subscription-sharing user by exact share user ID.",
"subscription.follow.delete": "Stop following one subscription-sharing user by exact share user ID.",
@@ -230,6 +237,7 @@ FIELD_DESCRIPTIONS = {
"apikey": "Site API key used by sites that support API-key authentication.",
"area": "Optional region filter applied by the torrent search workflow.",
"artist_id": "Source-native artist ID returned by music search or an album detail response.",
"artist_name": "Artist name preserved as the identity label for an artist-wide music collection.",
"audio_format": "Requested or recorded audio container or codec, such as FLAC or MP3.",
"audio_lossless": "Whether the recorded audio result is lossless.",
"audio_quality": "Subscription audio-quality rule, such as hires, lossless, or lossy.",
@@ -246,7 +254,7 @@ FIELD_DESCRIPTIONS = {
"scalar and object request bodies are not interchangeable."
),
"category": "MoviePilot media category or filter-group category, depending on the operation.",
"cache_key": "Exact recognition-cache key returned by music.cache.get.",
"cache_key": "Exact recognition-cache key returned by the corresponding recognition-cache get operation.",
"channel": "Message channel that originally submitted the download.",
"children": "Child storage items nested below this item.",
"code": "Two-factor verification code or site-specific authentication secret.",
@@ -331,6 +339,7 @@ FIELD_DESCRIPTIONS = {
"limit_count": "Maximum number of site requests allowed in one rate-limit interval.",
"limit_interval": "Number of requests in the site's rate-limit window.",
"limit_seconds": "Site rate-limit window length in seconds.",
"limit": "Maximum number of recent subscription-search batches to return.",
"logid": "One download-history or transfer-log identifier used by manual transfer.",
"logids": "Multiple download-history or transfer-log identifiers included in manual transfer.",
"match_field": "Object field used to match one list item during an upsert or removal.",
@@ -624,6 +633,9 @@ FIELD_DESCRIPTIONS.update(
MODEL_DESCRIPTIONS = {
"AgentCommandRunRequest": "Slash-command execution request.",
"Body_add_api_v1_download_add_post": "MoviePilot download submission request.",
"Body_download_artist_collection_api_v1_download_artist_collection_post": (
"Artist-wide music collection download submission request."
),
"CustomFilterRuleCreateRequest": "Custom filter-rule creation request.",
"CustomFilterRuleUpdateRequest": "Custom filter-rule update request.",
"CustomIdentifiersUpdateRequest": "Complete custom recognition-identifier replacement request.",

View File

@@ -449,6 +449,63 @@
"title": "Body_add_api_v1_download_add_post",
"type": "object"
},
"Body_download_artist_collection_api_v1_download_artist_collection_post": {
"description": "Artist-wide music collection download submission request.",
"properties": {
"artist_id": {
"description": "Source-native artist ID returned by music search or an album detail response.",
"minLength": 1,
"title": "Artist Id",
"type": "string"
},
"artist_name": {
"description": "Artist name preserved as the identity label for an artist-wide music collection.",
"minLength": 1,
"title": "Artist Name",
"type": "string"
},
"downloader": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Configured downloader instance name.",
"title": "Downloader"
},
"media_source": {
"$ref": "#/$defs/MediaSource",
"description": "Metadata source identifier. Preserve the exact value returned with media_id."
},
"save_path": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Configured downloader-side save path for the download or subscription.",
"title": "Save Path"
},
"torrent_in": {
"$ref": "#/$defs/TorrentInfo",
"description": "Complete torrent candidate returned by search.results or search.torrents."
}
},
"required": [
"artist_name",
"artist_id",
"media_source",
"torrent_in"
],
"title": "Body_download_artist_collection_api_v1_download_artist_collection_post",
"type": "object"
},
"Body_recommend_search_results_api_v1_search_recommend_post": {
"description": "Torrent search results and recommendation controls supplied to the configured model.",
"properties": {
@@ -7020,6 +7077,27 @@
"title": "download.add",
"type": "object"
},
{
"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.",
"properties": {
"body": {
"$ref": "#/$defs/Body_download_artist_collection_api_v1_download_artist_collection_post",
"description": "Request value for download.artist_collection. Submit one artist-wide music torrent to MoviePilot's collection download workflow. Use the exact type and fields below."
},
"operation_id": {
"const": "download.artist_collection",
"description": "Exact allowlisted MoviePilot operation ID selecting this oneOf branch.",
"type": "string"
}
},
"required": [
"operation_id",
"body"
],
"title": "download.artist_collection",
"type": "object"
},
{
"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.",
@@ -7778,6 +7856,70 @@
"total_count_field": null
}
},
{
"additionalProperties": false,
"description": "Clear the complete administrator-only TMDB recognition cache. Method: DELETE. Path: /api/v1/tmdb/cache. Effect: destructive_write.",
"properties": {
"operation_id": {
"const": "media.cache.clear",
"description": "Exact allowlisted MoviePilot operation ID selecting this oneOf branch.",
"type": "string"
}
},
"required": [
"operation_id"
],
"title": "media.cache.clear",
"type": "object"
},
{
"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.",
"properties": {
"operation_id": {
"const": "media.cache.delete",
"description": "Exact allowlisted MoviePilot operation ID selecting this oneOf branch.",
"type": "string"
},
"path_params": {
"additionalProperties": false,
"description": "Resource identity placeholders for media.cache.delete. Delete one administrator-only TMDB recognition-cache entry by exact key. Use only the named fields below.",
"properties": {
"cache_key": {
"description": "Exact recognition-cache key returned by the corresponding recognition-cache get operation.",
"title": "Cache Key",
"type": "string"
}
},
"required": [
"cache_key"
],
"type": "object"
}
},
"required": [
"operation_id",
"path_params"
],
"title": "media.cache.delete",
"type": "object"
},
{
"additionalProperties": false,
"description": "Inspect the administrator-only TMDB recognition cache and summary counts. Method: GET. Path: /api/v1/tmdb/cache. Effect: safe_read.",
"properties": {
"operation_id": {
"const": "media.cache.get",
"description": "Exact allowlisted MoviePilot operation ID selecting this oneOf branch.",
"type": "string"
}
},
"required": [
"operation_id"
],
"title": "media.cache.get",
"type": "object"
},
{
"additionalProperties": false,
"description": "Read the media-classification field catalog and source capabilities. Method: GET. Path: /api/v1/media/classification/fields. Effect: safe_read.",
@@ -9149,7 +9291,7 @@
"description": "Resource identity placeholders for music.cache.delete. Delete one administrator-only MusicBrainz recognition-cache entry by exact key. Use only the named fields below.",
"properties": {
"cache_key": {
"description": "Exact recognition-cache key returned by music.cache.get.",
"description": "Exact recognition-cache key returned by the corresponding recognition-cache get operation.",
"title": "Cache Key",
"type": "string"
}
@@ -12428,6 +12570,105 @@
"title": "subscription.delete_by_media",
"type": "object"
},
{
"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.",
"properties": {
"operation_id": {
"const": "subscription.execution.cancel",
"description": "Exact allowlisted MoviePilot operation ID selecting this oneOf branch.",
"type": "string"
},
"path_params": {
"additionalProperties": false,
"description": "Resource identity placeholders for subscription.execution.cancel. Request cancellation of one durable subscription-search batch before its download side-effect boundary. Use only the named fields below.",
"properties": {
"batch_id": {
"description": "Stable subscription search batch identifier.",
"title": "Batch Id",
"type": "string"
}
},
"required": [
"batch_id"
],
"type": "object"
}
},
"required": [
"operation_id",
"path_params"
],
"title": "subscription.execution.cancel",
"type": "object"
},
{
"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.",
"properties": {
"operation_id": {
"const": "subscription.execution.get",
"description": "Exact allowlisted MoviePilot operation ID selecting this oneOf branch.",
"type": "string"
},
"path_params": {
"additionalProperties": false,
"description": "Resource identity placeholders for subscription.execution.get. Read one durable subscription-search batch visible to the current user. Use only the named fields below.",
"properties": {
"batch_id": {
"description": "Stable subscription search batch identifier.",
"title": "Batch Id",
"type": "string"
}
},
"required": [
"batch_id"
],
"type": "object"
}
},
"required": [
"operation_id",
"path_params"
],
"title": "subscription.execution.get",
"type": "object"
},
{
"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.",
"properties": {
"operation_id": {
"const": "subscription.execution.list",
"description": "Exact allowlisted MoviePilot operation ID selecting this oneOf branch.",
"type": "string"
},
"query": {
"additionalProperties": false,
"description": "Filters and control values for subscription.execution.list. List recent durable subscription-search batches visible to the current user. Use only the named fields below.",
"properties": {
"limit": {
"default": 10,
"description": "Maximum number of recent subscription-search batches to return.",
"title": "Limit",
"type": "integer"
}
},
"type": "object"
}
},
"required": [
"operation_id"
],
"title": "subscription.execution.list",
"type": "object",
"x-moviepilot-collection": {
"body_shape": "list",
"default_pagination": "endpoint-defined",
"result_count_field": "collection.result_count",
"total_count_field": null
}
},
{
"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.",
@@ -15458,6 +15699,7 @@
"database.backups.list",
"database.backups.verify",
"download.add",
"download.artist_collection",
"download.clients",
"download.history.delete",
"download.history.list",
@@ -15475,6 +15717,9 @@
"filter.test",
"library.exists",
"library.latest",
"media.cache.clear",
"media.cache.delete",
"media.cache.get",
"media.classification.fields",
"media.classification.policy.get",
"media.classification.policy.history",
@@ -15576,6 +15821,9 @@
"subscription.add",
"subscription.delete",
"subscription.delete_by_media",
"subscription.execution.cancel",
"subscription.execution.get",
"subscription.execution.list",
"subscription.files",
"subscription.find",
"subscription.follow.add",

View File

@@ -56,25 +56,25 @@ MCP 当前不会主动发送工具列表变更通知(`listChanged=false`)。
`app/agent/policy/resources/api_mcp_schema.json` 是 `moviepilot_api` 的生成制品,不是设置项或 API 参数的手工事实源。`scripts/generate_agent_api_mcp_schema.py` 从当前 FastAPI OpenAPI、固定 operation 路由和 Agent 专用英文参数说明生成该文件;运行时直接读取它响应外部 MCP `tools/list`,测试会校验生成结果没有漂移。修改 API、请求模型或 operation 后应重新生成并提交该文件,不应直接编辑 JSON。
当前完整 FastAPI OpenAPI 包含 397 个 HTTP 操作,其中 211 个稳定业务操作进入
`moviepilot_api`,使用 209 个固定路由模板:208 条 OpenAPI 路由直接匹配,另有 1 条只允许
当前完整 FastAPI OpenAPI 包含 397 个 HTTP 操作,其中 218 个稳定业务操作进入
`moviepilot_api`,使用 216 个固定路由模板:215 条 OpenAPI 路由直接匹配,另有 1 条只允许
`tmdb`、`douban`、`bangumi`、`anilist` 四个来源的受限人物作品动态路由。每个 operation
均同时具备固定 method/path、角色权限、副作用等级、确认与恢复策略、结果敏感性、英文用途说明,
以及可直接提交的 path/query/body JSON Schema;Skill front matter、正文 operation 章节、运行时
注册表和 MCP `tools/list` 的 211 个 `oneOf` 分支必须完全一致。
注册表和 MCP `tools/list` 的 218 个 `oneOf` 分支必须完全一致。
数量不相等是明确的安全与语义边界,而不是漏生成。当前 397 条路由均被审计并锁定为以下一种
归属,审计生成器不再提供“未归类”兜底:
| 归属 | 数量 | Agent 使用方式 |
| :--- | ---: | :--- |
| `gateway` | 208 | 通过 `moviepilot_api` 的稳定 operation 和精确参数合同调用 |
| `consolidated` | 74 | 通过同领域聚合 operation 调用,不复制数据源或前端专用路由 |
| `provider-skill` | 13 | 通过下载器或媒体服务器 Skill 调用第三方 provider API |
| `gateway` | 215 | 通过 `moviepilot_api` 的稳定 operation 和精确参数合同调用 |
| `consolidated` | 71 | 通过同领域聚合 operation 调用,不复制数据源或前端专用路由 |
| `provider-skill` | 12 | 通过下载器或媒体服务器 Skill 调用第三方 provider API |
| `alternate-auth-duplicate` | 11 | 使用对应 bearer-authenticated gateway operation,不暴露 API_TOKEN 兼容副本 |
| `transport_or_identity` | 66 | 由登录、令牌、MCP、会话、回调、健康检查等宿主传输/身份边界拥有 |
| `stream_or_binary` | 10 | 由直接客户端处理流式日志、消息、文件、图片等非结构化响应 |
| `ui_presentation` | 15 | 由前端或插件渲染面拥有,不作为业务 Agent operation |
| `ui_presentation` | 12 | 由前端或插件渲染面拥有,不作为业务 Agent operation |
逐路由归属见 `docs/refactor/agent-api-surface-audit.md`,并由
`tests/test_agent_api_surface_audit.py` 对当前 OpenAPI、固定注册表、MCP schema、英文 Skill
@@ -454,12 +454,12 @@ AMLL 使用无需鉴权的原生搜索与获取接口,先尝试 ISRC,再核
| 方法 | 路径 | 说明 |
| :--- | :--- | :--- |
| GET | `/api/v1/tmdb/cache` | 查询 TheMovieDb 识别缓存统计、共享识别累计成功命中次数及开关状态 |
| DELETE | `/api/v1/tmdb/cache/{cache_key}` | 按缓存键删除单条 TheMovieDb 识别缓存,缓存键需要进行 URL 编码 |
| DELETE | `/api/v1/tmdb/cache` | 清空全部 TheMovieDb 识别缓存 |
| GET | `/api/v1/music/cache` | 查询 MusicBrainz 音乐识别缓存统计及条目列表 |
| DELETE | `/api/v1/music/cache/{cache_key}` | 按缓存键删除单条音乐识别缓存,缓存键需要进行 URL 编码 |
| DELETE | `/api/v1/music/cache` | 清空全部音乐识别缓存 |
| GET | `/api/v1/tmdb/cache` | `media.cache.get`:查询 TheMovieDb 识别缓存统计、共享识别累计成功命中次数及开关状态 |
| DELETE | `/api/v1/tmdb/cache/{cache_key}` | `media.cache.delete`:按缓存键删除单条 TheMovieDb 识别缓存,缓存键需要进行 URL 编码 |
| DELETE | `/api/v1/tmdb/cache` | `media.cache.clear`:清空全部 TheMovieDb 识别缓存 |
| GET | `/api/v1/music/cache` | `music.cache.get`:查询 MusicBrainz 音乐识别缓存统计及条目列表 |
| DELETE | `/api/v1/music/cache/{cache_key}` | `music.cache.delete`:按缓存键删除单条音乐识别缓存,缓存键需要进行 URL 编码 |
| DELETE | `/api/v1/music/cache` | `music.cache.clear`:清空全部音乐识别缓存 |
TMDB 缓存查询响应的 `data` 包含 `count`、`recognized`、`unrecognized`、`data`,以及共享识别统计字段
`shared_recognized` 和开关字段 `shared_recognize_enabled`。共享命中次数仅在共享结果驱动的二次媒体识别成功后累计。
@@ -470,6 +470,18 @@ TMDB 缓存查询响应的 `data` 包含 `count`、`recognized`、`unrecognized`
旧版未包含这些证据的派生缓存在升级后重新建立,不影响下载历史或订阅数据。
名称确认规则更新时同样重建旧派生缓存,避免艺术家前后缀误截断的旧结果继续命中。
### 订阅搜索执行批次
订阅搜索会持久化为可恢复的执行批次,Agent 可以使用以下 `moviepilot_api` operation 查询或停止当前用户可见的批次:
| Operation | 方法 | 路径 | 说明 |
| :--- | :--- | :--- | :--- |
| `subscription.execution.list` | GET | `/api/v1/subscribe/execution/batches` | 查询最近的订阅搜索批次,`limit` 默认 10 |
| `subscription.execution.get` | GET | `/api/v1/subscribe/execution/batches/{batch_id}` | 查询一个批次的状态、进度、恢复信息和最终结果 |
| `subscription.execution.cancel` | PUT | `/api/v1/subscribe/execution/batches/{batch_id}/cancel` | 在下载副作用边界前请求取消一个批次 |
取消是幂等的状态请求,不会撤销已经提交到下载器的任务;批次详情中的状态和任务结果才是最终事实。
### 单条订阅搜索周期
`POST /api/v1/subscribe/` 和 `PUT /api/v1/subscribe/` 支持 `search_interval`:
@@ -600,8 +612,9 @@ Web Agent 直接调用 `moviepilot_api` 时,宿主会自动加载 `moviepilot-
| :--- | :--- |
| 媒体/搜索 | `media.search`、`media.person.search`、`media.person.credits`、`media.recognize`、`media.scrape`、`media.episode_schedule`、`media.detail`、`search.torrents`、`search.results`、`recommendation.list` |
| 媒体自动分类 | `media.classification.fields`、`media.classification.policy.get`、`media.classification.policy.validate`、`media.classification.policy.preview`、`media.classification.policy.impact`、`media.classification.policy.history`、`media.classification.policy.update`、`media.classification.policy.rollback` |
| 订阅 | `subscription.add`、`subscription.update`、`subscription.search`、`subscription.list`、`subscription.shares`、`subscription.popular`、`subscription.history`、`subscription.delete` |
| 下载/历史 | `download.add`、`download.history.delete`、`transfer.history.delete` |
| 订阅 | `subscription.add`、`subscription.update`、`subscription.search`、`subscription.list`、`subscription.shares`、`subscription.popular`、`subscription.history`、`subscription.delete`、`subscription.execution.list`、`subscription.execution.get`、`subscription.execution.cancel` |
| 下载/历史 | `download.add`、`download.artist_collection`、`download.history.delete`、`transfer.history.delete` |
| 媒体缓存 | `media.cache.get`、`media.cache.delete`、`media.cache.clear`、`music.cache.get`、`music.cache.delete`、`music.cache.clear` |
| 媒体库/存储/转移 | `library.exists`、`storage.settings`、`storage.list`、`transfer.history`、`transfer.file` |
| 站点 | `site.list`、`site.update`、`site.userdata`、`site.test`、`site.cookie.update` |
| 调度/工作流 | `scheduler.list`、`scheduler.run`、`workflow.list`、`workflow.run` |

View File

@@ -1,12 +1,12 @@
{
"disposition_counts": {
"alternate-auth-duplicate": 11,
"consolidated": 74,
"gateway": 208,
"provider-skill": 13,
"consolidated": 71,
"gateway": 215,
"provider-skill": 12,
"stream_or_binary": 10,
"transport_or_identity": 66,
"ui_presentation": 15
"ui_presentation": 12
},
"dynamic_gateway_routes": [
{
@@ -18,9 +18,9 @@
"reason": "The executor validates and expands this bounded source placeholder to one of tmdb, douban, bangumi, or anilist before calling the corresponding concrete OpenAPI route."
}
],
"gateway_http_route_count": 209,
"gateway_operation_count": 211,
"matched_gateway_http_route_count": 208,
"gateway_http_route_count": 216,
"gateway_operation_count": 218,
"matched_gateway_http_route_count": 215,
"openapi_operation_count": 397,
"operations": [
{
@@ -640,12 +640,14 @@
]
},
{
"disposition": "provider-skill",
"disposition": "gateway",
"method": "POST",
"operation_ids": [],
"owner": "downloader-operation",
"operation_ids": [
"download.artist_collection"
],
"owner": "moviepilot-api",
"path": "/api/v1/download/artist-collection",
"reason": "Low-level provider behavior is exposed by the self-describing provider Skill; high-level MoviePilot operations remain in moviepilot-api.",
"reason": "Executable through moviepilot_api; exact inputs are generated into MCP tools/list and SKILL.md.",
"summary": "添加艺术家合集下载",
"tags": [
"download"
@@ -3616,36 +3618,42 @@
]
},
{
"disposition": "ui_presentation",
"disposition": "gateway",
"method": "GET",
"operation_ids": [],
"owner": "host-ui",
"operation_ids": [
"subscription.execution.list"
],
"owner": "moviepilot-api",
"path": "/api/v1/subscribe/execution/batches",
"reason": "Background subscription execution status and cancellation are owned by the authenticated frontend workflow; they are not yet a stable Agent gateway contract.",
"reason": "Executable through moviepilot_api; exact inputs are generated into MCP tools/list and SKILL.md.",
"summary": "查看订阅搜索进度",
"tags": [
"subscribe"
]
},
{
"disposition": "ui_presentation",
"disposition": "gateway",
"method": "GET",
"operation_ids": [],
"owner": "host-ui",
"operation_ids": [
"subscription.execution.get"
],
"owner": "moviepilot-api",
"path": "/api/v1/subscribe/execution/batches/{batch_id}",
"reason": "Background subscription execution status and cancellation are owned by the authenticated frontend workflow; they are not yet a stable Agent gateway contract.",
"reason": "Executable through moviepilot_api; exact inputs are generated into MCP tools/list and SKILL.md.",
"summary": "查看一次订阅搜索",
"tags": [
"subscribe"
]
},
{
"disposition": "ui_presentation",
"disposition": "gateway",
"method": "PUT",
"operation_ids": [],
"owner": "host-ui",
"operation_ids": [
"subscription.execution.cancel"
],
"owner": "moviepilot-api",
"path": "/api/v1/subscribe/execution/batches/{batch_id}/cancel",
"reason": "Background subscription execution status and cancellation are owned by the authenticated frontend workflow; they are not yet a stable Agent gateway contract.",
"reason": "Executable through moviepilot_api; exact inputs are generated into MCP tools/list and SKILL.md.",
"summary": "停止一次订阅搜索",
"tags": [
"subscribe"
@@ -4502,36 +4510,42 @@
]
},
{
"disposition": "consolidated",
"disposition": "gateway",
"method": "DELETE",
"operation_ids": [],
"operation_ids": [
"media.cache.clear"
],
"owner": "moviepilot-api",
"path": "/api/v1/tmdb/cache",
"reason": "Source-specific or presentation-oriented route is represented by a stable aggregate search, detail, person, recommendation, or music operation instead of duplicating every frontend route.",
"reason": "Executable through moviepilot_api; exact inputs are generated into MCP tools/list and SKILL.md.",
"summary": "清空 TheMovieDb 识别缓存",
"tags": [
"tmdb"
]
},
{
"disposition": "consolidated",
"disposition": "gateway",
"method": "GET",
"operation_ids": [],
"operation_ids": [
"media.cache.get"
],
"owner": "moviepilot-api",
"path": "/api/v1/tmdb/cache",
"reason": "Source-specific or presentation-oriented route is represented by a stable aggregate search, detail, person, recommendation, or music operation instead of duplicating every frontend route.",
"reason": "Executable through moviepilot_api; exact inputs are generated into MCP tools/list and SKILL.md.",
"summary": "查询 TheMovieDb 识别缓存",
"tags": [
"tmdb"
]
},
{
"disposition": "consolidated",
"disposition": "gateway",
"method": "DELETE",
"operation_ids": [],
"operation_ids": [
"media.cache.delete"
],
"owner": "moviepilot-api",
"path": "/api/v1/tmdb/cache/{cache_key}",
"reason": "Source-specific or presentation-oriented route is represented by a stable aggregate search, detail, person, recommendation, or music operation instead of duplicating every frontend route.",
"reason": "Executable through moviepilot_api; exact inputs are generated into MCP tools/list and SKILL.md.",
"summary": "删除指定 TheMovieDb 识别缓存",
"tags": [
"tmdb"

View File

@@ -6,9 +6,9 @@
## Result
- OpenAPI HTTP operations: **397**
- Stable `moviepilot_api` operations: **211**
- Exact HTTP routes used by the gateway: **209**
- OpenAPI routes matched directly by the gateway: **208**
- Stable `moviepilot_api` operations: **218**
- Exact HTTP routes used by the gateway: **216**
- OpenAPI routes matched directly by the gateway: **215**
- Bounded dynamic gateway routes: **1**
- Every gateway operation has a generated English oneOf input contract in MCP `tools/list` and `skills/moviepilot-api/SKILL.md`.
- Every non-gateway OpenAPI operation is listed below with an explicit ownership boundary; it is not silently callable through arbitrary URL/method input.
@@ -18,12 +18,12 @@
| disposition | count | meaning |
| :--- | ---: | :--- |
| `alternate-auth-duplicate` | 11 | API-token compatibility duplicate of a bearer-authenticated capability. |
| `consolidated` | 74 | Source/UI route represented by a stable aggregate Agent operation. |
| `gateway` | 208 | Approved structured MoviePilot Agent operation. |
| `provider-skill` | 13 | Low-level downloader or media-server capability owned by a provider Skill. |
| `consolidated` | 71 | Source/UI route represented by a stable aggregate Agent operation. |
| `gateway` | 215 | Approved structured MoviePilot Agent operation. |
| `provider-skill` | 12 | Low-level downloader or media-server capability owned by a provider Skill. |
| `stream_or_binary` | 10 | Streaming or binary response owned by a direct client transport. |
| `transport_or_identity` | 66 | Authentication, protocol, callback, account, or conversation transport boundary. |
| `ui_presentation` | 15 | Frontend or plugin-rendered presentation contract. |
| `ui_presentation` | 12 | Frontend or plugin-rendered presentation contract. |
## Bounded Dynamic Routes
@@ -84,7 +84,7 @@
| `GET` | `/api/v1/download/` | download | `gateway` | download.tasks.active | 正在下载 |
| `POST` | `/api/v1/download/` | download | `consolidated` | download.add | 添加下载(含媒体信息) |
| `POST` | `/api/v1/download/add` | download | `gateway` | download.add | 添加下载(不含媒体信息) |
| `POST` | `/api/v1/download/artist-collection` | download | `provider-skill` | downloader-operation | 添加艺术家合集下载 |
| `POST` | `/api/v1/download/artist-collection` | download | `gateway` | download.artist_collection | 添加艺术家合集下载 |
| `GET` | `/api/v1/download/clients` | download | `gateway` | download.clients | 查询可用下载器 |
| `GET` | `/api/v1/download/paths` | download | `gateway` | download.paths | 查询可用下载路径 |
| `GET` | `/api/v1/download/start/{hashString}` | download | `provider-skill` | downloader-operation | 开始任务 |
@@ -310,9 +310,9 @@
| `POST` | `/api/v1/subscribe/` | subscribe | `gateway` | subscription.add | 新增订阅 |
| `PUT` | `/api/v1/subscribe/` | subscribe | `gateway` | subscription.update | 更新订阅 |
| `POST` | `/api/v1/subscribe/check` | subscribe | `gateway` | subscription.metadata.refresh | 刷新订阅 TMDB 信息 |
| `GET` | `/api/v1/subscribe/execution/batches` | subscribe | `ui_presentation` | host-ui | 查看订阅搜索进度 |
| `GET` | `/api/v1/subscribe/execution/batches/{batch_id}` | subscribe | `ui_presentation` | host-ui | 查看一次订阅搜索 |
| `PUT` | `/api/v1/subscribe/execution/batches/{batch_id}/cancel` | subscribe | `ui_presentation` | host-ui | 停止一次订阅搜索 |
| `GET` | `/api/v1/subscribe/execution/batches` | subscribe | `gateway` | subscription.execution.list | 查看订阅搜索进度 |
| `GET` | `/api/v1/subscribe/execution/batches/{batch_id}` | subscribe | `gateway` | subscription.execution.get | 查看一次订阅搜索 |
| `PUT` | `/api/v1/subscribe/execution/batches/{batch_id}/cancel` | subscribe | `gateway` | subscription.execution.cancel | 停止一次订阅搜索 |
| `GET` | `/api/v1/subscribe/files/{subscribe_id}` | subscribe | `gateway` | subscription.files | 订阅相关文件信息 |
| `DELETE` | `/api/v1/subscribe/follow` | subscribe | `gateway` | subscription.follow.delete | 取消Follow订阅分享人 |
| `GET` | `/api/v1/subscribe/follow` | subscribe | `gateway` | subscription.follow.list | 查询已Follow的订阅分享人 |
@@ -375,9 +375,9 @@
| `POST` | `/api/v1/system/upgrade` | system | `gateway` | system.upgrade.dev | Dev 更新并重启系统 |
| `GET` | `/api/v1/system/usage/statistic` | system | `gateway` | system.usage.statistics | 查询安装版本统计报表 |
| `GET` | `/api/v1/system/versions` | system | `gateway` | system.versions | 查询Github所有Release版本 |
| `DELETE` | `/api/v1/tmdb/cache` | tmdb | `consolidated` | moviepilot-api | 清空 TheMovieDb 识别缓存 |
| `GET` | `/api/v1/tmdb/cache` | tmdb | `consolidated` | moviepilot-api | 查询 TheMovieDb 识别缓存 |
| `DELETE` | `/api/v1/tmdb/cache/{cache_key}` | tmdb | `consolidated` | moviepilot-api | 删除指定 TheMovieDb 识别缓存 |
| `DELETE` | `/api/v1/tmdb/cache` | tmdb | `gateway` | media.cache.clear | 清空 TheMovieDb 识别缓存 |
| `GET` | `/api/v1/tmdb/cache` | tmdb | `gateway` | media.cache.get | 查询 TheMovieDb 识别缓存 |
| `DELETE` | `/api/v1/tmdb/cache/{cache_key}` | tmdb | `gateway` | media.cache.delete | 删除指定 TheMovieDb 识别缓存 |
| `GET` | `/api/v1/tmdb/collection/{collection_id}` | tmdb | `consolidated` | moviepilot-api | 系列合集详情 |
| `GET` | `/api/v1/tmdb/credits/{tmdbid}/{type_name}` | tmdb | `consolidated` | moviepilot-api | 演员阵容 |
| `GET` | `/api/v1/tmdb/person/credits/{person_id}` | tmdb | `consolidated` | moviepilot-api | 人物参演作品 |

View File

@@ -13,23 +13,24 @@ allowed-api-operations: >-
media.search media.person.search media.person.credits media.recognize media.scrape
media.episode_schedule media.detail subscription.add subscription.update subscription.search
subscription.list subscription.shares subscription.popular subscription.history
subscription.delete download.add download.tasks.active download.clients download.paths
download.history.list download.history.delete transfer.history.delete site.update site.list
site.userdata site.test site.cookie.update recommendation.list library.exists library.latest
storage.settings storage.list transfer.history transfer.file scheduler.list scheduler.run
workflow.list workflow.run plugin.installed plugin.market plugin.capabilities plugin.config.get
plugin.config.update plugin.source.options plugin.source.install plugin.source.change
plugin.reload plugin.install plugin.uninstall slash.list config.identifiers.get
config.identifiers.update search.torrents search.results filter.builtin filter.custom
filter.groups filter.custom.add filter.custom.update filter.custom.delete filter.group.add
filter.group.update filter.group.delete plugin.data config.system.get config.system.update
slash.run music.recognize music.explore music.album.get music.album.related music.artist.get
music.artist.albums music.artist.related music.cache.get music.cache.delete music.cache.clear
system.versions system.update.status system.update.check system.update.download system.restart
system.update.install system.upgrade.dev dashboard.media.statistics dashboard.storage
dashboard.processes dashboard.system dashboard.downloader scheduler.progress
dashboard.transfer.statistics dashboard.cpu dashboard.memory dashboard.network media.sources
media.recognize_file media.classification.fields media.classification.policy.get
subscription.delete download.add download.artist_collection download.tasks.active
download.clients download.paths download.history.list download.history.delete
transfer.history.delete site.update site.list site.userdata site.test site.cookie.update
recommendation.list library.exists library.latest storage.settings storage.list transfer.history
transfer.file scheduler.list scheduler.run workflow.list workflow.run plugin.installed
plugin.market plugin.capabilities plugin.config.get plugin.config.update plugin.source.options
plugin.source.install plugin.source.change plugin.reload plugin.install plugin.uninstall
slash.list config.identifiers.get config.identifiers.update search.torrents search.results
filter.builtin filter.custom filter.groups filter.custom.add filter.custom.update
filter.custom.delete filter.group.add filter.group.update filter.group.delete plugin.data
config.system.get config.system.update slash.run music.recognize music.explore music.album.get
music.album.related music.artist.get music.artist.albums music.artist.related music.cache.get
music.cache.delete music.cache.clear system.versions system.update.status system.update.check
system.update.download system.restart system.update.install system.upgrade.dev
dashboard.media.statistics dashboard.storage dashboard.processes dashboard.system
dashboard.downloader scheduler.progress dashboard.transfer.statistics dashboard.cpu
dashboard.memory dashboard.network media.sources media.recognize_file media.cache.get
media.cache.delete media.cache.clear media.classification.fields media.classification.policy.get
media.classification.policy.validate media.classification.policy.preview
media.classification.policy.impact media.classification.policy.history
media.classification.policy.update media.classification.policy.rollback media.episode_groups
@@ -40,10 +41,11 @@ allowed-api-operations: >-
site.statistic site.mapping site.supporting subscription.get subscription.find
subscription.delete_by_media subscription.status.update subscription.reset
subscription.search_all subscription.refresh subscription.metadata.refresh
subscription.history.delete subscription.user.list subscription.files subscription.share
subscription.share.delete subscription.fork subscription.follow.list subscription.follow.add
subscription.follow.delete subscription.share.statistics storage.manage storage.mkdir
storage.rename storage.delete transfer.queue transfer.queue.delete transfer.name
subscription.history.delete subscription.user.list subscription.files
subscription.execution.list subscription.execution.get subscription.execution.cancel
subscription.share subscription.share.delete subscription.fork subscription.follow.list
subscription.follow.add subscription.follow.delete subscription.share.statistics storage.manage
storage.mkdir storage.rename storage.delete transfer.queue transfer.queue.delete transfer.name
transfer.target_path transfer.manual_history transfer.episode_format.recommend
transfer.manual_reviews transfer.manual_review transfer.manual_review.resolve
transfer.history.redo transfer.history.redo_batch transfer.history.clear workflow.create

View File

@@ -11,6 +11,13 @@ Purpose: Submit one torrent to MoviePilot's normal download workflow.
- `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 for the download or subscription.; `torrent_in*` (TorrentInfo): Complete torrent candidate returned by search.results or search.torrents.
### `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.
- `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 for the download or subscription.; `torrent_in*` (TorrentInfo): Complete torrent candidate returned by search.results or search.torrents.
### `download.clients`
`GET /api/v1/download/clients`; policy effect: `safe_read`.
Purpose: List enabled downloader instance names and provider types without credentials.

View File

@@ -30,6 +30,27 @@ other clients.
## Operations
### `media.cache.clear`
`DELETE /api/v1/tmdb/cache`; policy effect: `destructive_write`.
Purpose: Clear the complete administrator-only TMDB recognition cache.
- `path_params`: none
- `query`: none
- `body`: none
### `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.
- `path_params`: `cache_key*` (string): Exact recognition-cache key returned by the corresponding recognition-cache get operation.
- `query`: none
- `body`: none
### `media.cache.get`
`GET /api/v1/tmdb/cache`; policy effect: `safe_read`.
Purpose: Inspect the administrator-only TMDB recognition cache and summary counts.
- `path_params`: none
- `query`: none
- `body`: none
### `media.classification.fields`
`GET /api/v1/media/classification/fields`; policy effect: `safe_read`.
Purpose: Read the media-classification field catalog and source capabilities.

View File

@@ -83,7 +83,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.
- `path_params`: `cache_key*` (string): Exact recognition-cache key returned by music.cache.get.
- `path_params`: `cache_key*` (string): Exact recognition-cache key returned by the corresponding recognition-cache get operation.
- `query`: none
- `body`: none

View File

@@ -25,6 +25,28 @@ Purpose: Delete accessible subscriptions matching one canonical media identity.
- `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
### `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.
- `path_params`: `batch_id*` (string): Stable subscription search batch identifier.
- `query`: none
- `body`: none
### `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.
- `path_params`: `batch_id*` (string): Stable subscription search batch identifier.
- `query`: none
- `body`: none
### `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.
- `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.
- `body`: none
### `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.

View File

@@ -30,12 +30,12 @@ from app.agent.tools.manager import MoviePilotToolsManager
def test_api_operation_registry_matches_migration_batches() -> None:
"""API 操作注册表必须覆盖各迁移批次且每项具有固定路由。"""
assert len(API_FIRST_BATCH_OPERATION_SPECS) == 52
assert len(API_FIRST_BATCH_OPERATION_SPECS) == 53
assert len(API_PARITY_OPERATION_SPECS) == 15
assert len(API_MUSIC_OPERATION_SPECS) == 10
assert len(API_SYSTEM_OPERATION_SPECS) == 7
assert len(API_EXTENDED_OPERATION_SPECS) == 127
assert len(API_OPERATION_SPECS) == 211
assert len(API_EXTENDED_OPERATION_SPECS) == 133
assert len(API_OPERATION_SPECS) == 218
assert {spec.operation_id for spec in API_OPERATION_SPECS} == set(API_OPERATION_ROUTES)
assert {
"download.list",

View File

@@ -60,6 +60,29 @@ def test_cleanup_confirmation_stays_in_authenticated_management_workflow() -> No
assert (disposition, owner, operations) == ("ui_presentation", "host-ui", [])
def test_recent_business_routes_are_exposed_as_stable_gateway_operations() -> None:
"""近期高层业务端点必须保持在 moviepilot_api,而不是落入边界归属。"""
audit = json.loads(AUDIT_JSON.read_text(encoding="utf-8"))
routes = {(item["method"], item["path"]): item for item in audit["operations"]}
expected = {
("POST", "/api/v1/download/artist-collection"): "download.artist_collection",
("GET", "/api/v1/tmdb/cache"): "media.cache.get",
("DELETE", "/api/v1/tmdb/cache/{cache_key}"): "media.cache.delete",
("DELETE", "/api/v1/tmdb/cache"): "media.cache.clear",
("GET", "/api/v1/subscribe/execution/batches"): "subscription.execution.list",
("GET", "/api/v1/subscribe/execution/batches/{batch_id}"): "subscription.execution.get",
(
"PUT",
"/api/v1/subscribe/execution/batches/{batch_id}/cancel",
): "subscription.execution.cancel",
}
for route, operation_id in expected.items():
item = routes[route]
assert item["disposition"] == "gateway"
assert item["operation_ids"] == [operation_id]
def test_moviepilot_api_skill_routes_contracts_to_category_files() -> None:
"""The API Skill entrypoint stays concise and every operation has a namespace file."""
entrypoint = API_SKILL.read_text(encoding="utf-8")