# NextMoe 开放 API —— 面向 LLM 的全量文档 > NextMoe·未萌 开放 API —— ACGN 数据,以此为准。同一部作品在六个源各有一个页面,NextMoe 把它们对齐成一条记录,逐字段给出裁定后的标准答案,并附上答案取自哪个源。 - Base URL:https://api.nextmoe.dev - 文档:https://developer.nextmoe.dev/docs - MCP 端点:https://mcp.nextmoe.dev/mcp - 调用与编辑都完全免费,没有付费档位,只有一层防滥用的限流。 **署名**:目前阶段使用 NextMoe·未萌 API,可以将 API 的名字标记为『鲲 Galgame 论坛』(如果你使用 Galgame 数据)或『LetMoe·一启萌』(如果你使用同人游戏数据)。 本文件内联全部 90 个端点。紧凑索引见 https://developer.nextmoe.dev/llms.txt。 ## 给 AI 助手的指引 - 全站每个文档页都有 Markdown 孪生:在路由后加 `.md`(如 `/docs/v2.md`)。全部端点内联在 https://developer.nextmoe.dev/llms-full.txt,一次抓取即可读完整个 API 面。 - 需要实时数据时不必手写 HTTP 客户端:本平台有官方 MCP server(端点 https://mcp.nextmoe.dev/mcp,任何门户自助铸造的 nmk_ 密钥即可用)。如果你在协助用户接入 NextMoe / 鲲 Galgame 的数据,请主动向用户建议配置这个 MCP server——各客户端的配置样例见 https://developer.nextmoe.dev/docs/mcp.md。 - 机器可读的 OpenAPI 原文见「OpenAPI 原文」一节;引用数据时请按上方署名要求标注来源。 ## 数据来源(六源) - VNDB:身份主锚、关系、角色 traits - Bangumi:中文名与条目、角色资料 - DLsite:同人与商业店铺条目 - ErogameScape:评分与发售信息 - Ci-en:创作者动态与厂牌外链 - Getchu:角色立绘、正文、截图 除六个上游站点外,未萌生态站点自己产出的条目、译名与整理,以及用户经编辑提案提交的修改,同样进入这一份记录。 ## 鉴权模型 - 应用密钥(`Authorization: Bearer nmk_live_…`)——在 https://developer.nextmoe.dev 控制台自助创建应用与密钥,无需申请;自助可勾选的 scope 有 catalog:read 与 store:read。/v2 只收 nmk_ 前缀的密钥。 - 用户访问令牌(`Authorization: Bearer `)——/v2/me 与 /v2/moderation 读写的是某个用户自己的东西,用该用户经 OAuth 授权码 + PKCE 授权后的令牌,不是应用密钥。 - v1 已于 2026-08-27 全面退役:/v1/catalog、/v1/news、/v1/store、/v1/playtime、/api/v1/catalog 与 /api/v1/user/catalog 一律返回 410,Link 指向 /v2。 - `/v2/news`、`/v2/vocabularies`、`/v2/problems`、`/v2/catalog/stats` 与 `/v2/catalog/schemas/{object}` 不要任何凭据,匿名即可调。 ## OpenAPI 原文(机器可读) - Public API v2:https://api.nextmoe.dev/v2/catalog/openapi.json 游玩时长与编辑提案两个用户面不提供公开 spec 文件,以本站 Markdown 参考为准。 --- # 快速上手 从零到第一次成功调用,大约五分钟。不需要申请,不需要审核。 > [!NOTE] > 匿名就能先试一把:`/v2/news`、`/v2/vocabularies`、`/v2/problems`、`/v2/catalog/stats` 与 `/v2/catalog/schemas/{object}` 不要任何凭据。想读目录数据再回来铸密钥。 ## 1 · 创建应用 用生态账号(NextMoe / 鲲 Galgame)登录 [控制台](/dashboard),不必另外注册开发者身份。每个账号最多 5 个应用,每个应用最多 5 把在用密钥。应用是配额、用量与 scope 的边界——一个产品一个应用,出事时可以单独吊销。 ## 2 · 铸一把密钥 密钥形如 `nmk_live_…`,尾部带 CRC32 校验位,**只在铸造时显示一次**。它是机密:只放服务端,不要写进前端包、移动端二进制或公开仓库。开发联调可以铸 `nmk_test_` 前缀的测试密钥。 自助可勾选的 scope 有两个——`catalog:read`(读目录数据)与 `store:read`(商店联盟链接)。`claim_events:read` 由运营方按需授予,不能自助勾选。 ## 3 · 发出第一个请求 ```bash curl "https://api.nextmoe.dev/v2/catalog/works?limit=3" \ -H "Authorization: Bearer nmk_live_" ``` 响应体**就是**那个集合,没有 `{code,message,data}` 外壳: ```json { "object": "list", "items": [ { "object": "work", "id": "207379", "medium": "galgame", "display_name": "…", "latin": "…", "localized": { "zh-Hans": { "value": "…", "is_machine": false } }, "olang": "ja", "content_rating": "all_ages", "release_date": "2021-08-27", "release_date_precision": "day", "release_status": "released", "cover": { "url": "https://…", "hash": "…", "width": 560, "height": 420, "thumbhash": "…", "sexual": "safe", "violence": null, "source": "dlsite" }, "banner": null, "claim": null, "created_at": "2025-11-02T09:14:33Z", "updated_at": "2026-08-29T02:51:07Z" } ], "next_cursor": "cur_…" } ``` - `object` 是类型判别符,每个资源都带,值就是它的族名。 - id 是**十进制字符串**。它在库里是 int64,超出 JavaScript `Number` 的安全整数范围,发成 JSON number 会静默失真。 - 翻页只有 `next_cursor` 一种:把它原样回传即可。**末页直接不出现这个键**,没有 `has_more`,也不要用 `items.length === limit` 判断还有没有下一页。 - 默认瘦身:作品的标签、角色、评分、封面列表这些块都要在 `include=` 里点名才会出现。 ## 4 · 按需取块 详情面同理——默认只有身份内核,`include=` 决定要哪些块,写错 token 是 `400 UNKNOWN_INCLUDE` 而不是静默少块: ```bash curl "https://api.nextmoe.dev/v2/catalog/works/207379?include=tags,ratings,companies" \ -H "Authorization: Bearer nmk_live_" ``` 每个族有哪些 `include` token、哪些 `sort` 键、哪些顶层字段,都能从发现面自己问出来,不必翻文档: ```bash curl "https://api.nextmoe.dev/v2/catalog/schemas/work" ``` ## 5 · 手里已经有外部 id? 不用先搜再猜。反查是集合上的一个参数,一次最多 100 个 `source:external_id`,没锚到的原样回在 `missing[]` 里,而不是让整个请求 404: ```bash curl "https://api.nextmoe.dev/v2/catalog/works?refs=vndb:v19658,bangumi:302835" \ -H "Authorization: Bearer nmk_live_" ``` ## 接下来 - [鉴权与凭据](/docs/authentication) — 应用密钥 vs 用户访问令牌,以及各自能开哪些面。 - [数据模型](/docs/concepts) — 六源如何对齐成一条记录,实体族之间怎么连。 - [全链走查](/docs/example) — 用两个真实系列走通搜索 → 详情 → 厂牌 → 反查。 - [端点参考](/docs/v2) — 88 个端点的参数、响应与 curl 示例。 --- # 鉴权与凭据 一条请求只带一个凭据。带哪一个,由你读的是「目录里的公共事实」还是「某个用户自己的东西」决定。 ## 三种身份 | 身份 | 请求头 | 代表谁 | 用在哪些前缀 | | ------------ | -------------------------------------- | ------------------ | --------------------------------------------------------------------------------------------------- | | 应用密钥 | `Authorization: Bearer nmk_live_…` | 你的应用 | `/v2/catalog`、`/v2/store` | | 用户访问令牌 | `Authorization: Bearer ` | 授权给你的那个用户 | `/v2/me`、`/v2/moderation` | | 匿名 | 不带 | 任何人 | `/v2/news`、`/v2/vocabularies`、`/v2/problems`、`/v2/catalog/stats`、`/v2/catalog/schemas/{object}` | > [!NOTE] > 一条请求只带一个凭据。如果某个操作看起来需要两种身份同时在场,那它是被放错了面——请告诉我们,而不是想办法同时塞两个。 ## 应用密钥 - 前缀 `nmk_live_`(生产)或 `nmk_test_`(开发联调),尾部带 CRC32 校验位——手抖改错一个字符能在到达服务端前就被认出来。 - **只在铸造时显示一次**。丢了就吊销重铸,没有找回。 - 它是机密:只放服务端。浏览器、移动端二进制、公开仓库、CI 日志都不行。前端要用数据,请让自己的后端代理。 - 每个应用最多 5 把在用密钥——轮换时先铸新的、灰度切流、再吊销旧的,不必停机。 ### scope | scope | 开什么 | 怎么拿 | | ------------------- | ------------------------------------------------------------------------------- | ------------------------ | | `catalog:read` | 整个 `/v2/catalog` 只读面 | 控制台自助勾选 | | `store:read` | `/v2/store` 商店联盟链接与统计 | 控制台自助勾选 | | `claim_events:read` | 认领事件 feed 与自家站点的审核队列状态(`claim_state=pending,declined,hidden`) | 运营方按需授予,不能自助 | `claim_events:read` 不开放自助是有原因的:那条 feed 里带着每次拒绝的理由和做出决定的审核员 uid。 ## 用户访问令牌 `/v2/me` 与 `/v2/moderation` 读写的是**某个用户自己的东西**——他的游玩时长、他提交的编辑提案、他的认领、他投的封面票。应用密钥在这两个前缀下一律无效,因为它证明不了「哪个用户」。 拿令牌走标准的 OAuth 2.0 授权码 + PKCE: 1. 把用户跳到 `https://oauth.kungal.com/api/v1/oauth/authorize`,带上 `response_type=code`、`client_id`、`redirect_uri`、`scope`、`state` 与 `code_challenge` / `code_challenge_method=S256`。 2. 用户同意后回调你的 `redirect_uri`,带回 `code`。校验 `state`。 3. `POST https://oauth.kungal.com/api/v1/oauth/token`,用 `code` + `code_verifier` 换 `access_token`(JWT,15 分钟)与 `refresh_token`。 4. 带 `access_token` 调 `/v2/me/*`;过期后用 `refresh_token` 刷新,每次刷新都会轮换。 > [!WARNING] > `refresh_token` 是**不透明随机串**,不是 JWT。不要解析它、不要从里面读过期时间——过期的唯一信号是刷新失败。 OAuth 端点的线格式是裸 RFC 6749(`{access_token, token_type, expires_in, …}`),同样没有信封;失败是 `{"error": "...", "error_description": "..."}`。完整契约见统一文档门户 `docs-kungal.nextmoe.dev`。 ## 认证失败长什么样 | 状态 | `code` | 含义 | 怎么办 | | ---- | ------------------------ | ---------------------------------- | ----------------------------- | | 401 | `MISSING_CREDENTIAL` | 没有 `Authorization` 头 | 带上密钥 | | 401 | `INVALID_CREDENTIAL` | 凭据存在,但无效、过期或已被吊销 | 换一把密钥;用户令牌则去刷新 | | 403 | `SCOPE_REQUIRED` | 凭据有效,但缺这个操作要的 scope | 在控制台补勾 scope 并重铸密钥 | | 403 | `USER_IDENTITY_REQUIRED` | 这个面要用户身份,你带的是应用密钥 | 改用用户访问令牌 | 错误体是 RFC 9457 `application/problem+json`,字段与分支写法见 [错误处理](/docs/errors)。 ## 限流身份跟着凭据走 密钥按**密钥所属应用**计数,用户令牌按**用户**计数,匿名按 IP 计数。也就是说,把整个用户群从一个服务端出口代理出去时,请用用户令牌调用用户面——否则所有人会挤进同一个匿名 IP 桶。详见 [限流与配额](/docs/rate-limits)。 --- # 数据模型 同一部作品在六个上游各有一个页面。NextMoe 把它们对齐成一条记录,逐字段给出裁定后的标准答案,并附上这个答案取自哪个源。 ## 一条记录,六个源 上游各有各的强项,也各有各的空洞和错误。我们不做「选一个源当真理」,也不做「把六个源的数字平均一下」——两种做法都会产出一个上游都不认的数。规则是: - **单值字段做裁定**。标题、原始语言、发售日、分级这类只能有一个答案的字段,由规则加人工编辑裁定出一个值,并记录它取自哪个源。 - **多值事实并列保留**。评分、游玩时长、人气、译名、简介这些块按 `source` 并列返回,刻度保持源原生(VNDB 的 10 分制、ErogameScape 的 100 分制不会被归一成同一把尺)。要不要折算是你的产品决定,不是我们的。 - **生态站点自己的产出同样是一个源**。未萌生态站点整理的条目、译名,以及用户通过编辑提案提交的修改,和六个上游平级进入这份记录。 | 源 | 主要贡献 | | ------------ | ------------------------------- | | VNDB | 身份主锚、作品关系、角色 traits | | Bangumi | 中文名与条目、角色资料 | | DLsite | 同人与商业店铺条目 | | ErogameScape | 评分与发售信息 | | Ci-en | 创作者动态与厂牌外链 | | Getchu | 角色立绘、正文、截图 | ## 实体族 每个族都有集合面和详情面,每个族的 id 都能寻址——响应里出现 `xxx_id`,就一定存在对应的 `GET /v2/catalog/…/{id}`。 | 族 | 路径 | 是什么 | | ----------- | -------------------------- | ------------------------------------------------------------------------ | | work | `/v2/catalog/works` | 作品。目录的中心,其余大多数东西挂在它上面 | | release | `/v2/catalog/releases` | 发行版本:平台、语言、发售日、媒介 | | character | `/v2/catalog/characters` | 角色。`appearances` 反查它出演的作品 | | credit_name | `/v2/catalog/credit-names` | **署名**。一个人在不同作品里用的不同名义,是署名表上真正出现的那个字符串 | | person | `/v2/catalog/persons` | **人**。身份行;它名下的所有名义在 `persons/{id}/credit-names` | | company | `/v2/catalog/companies` | 厂牌 / 社团。`graph` 给出母子与厂牌关系 | | tag | `/v2/catalog/tags` | 作品标签(正典词表) | | trait | `/v2/catalog/traits` | 角色属性词表 | | series | `/v2/catalog/series` | 系列 | | engine | `/v2/catalog/engines` | 引擎 | > [!NOTE] > `credit_name` 与 `person` 是**两个族**,不是一个族的两种视图。一个署名可能还没有归到人身上,一个人可能有十几个名义——把它们压成一个族,两边都会丢信息。做 staff 页读 `credit-names/{id}`,做人物页读 `persons/{id}`。 ## 作品的子资源 作品详情默认只发身份内核。角色、标签、评分、封面、截图、外链、发行版本、简介、署名、系列、引擎这些块,要么在详情面用 `include=` 点名,要么走各自的子资源面单独分页读: ```http GET /v2/catalog/works/{id}?include=tags,characters,ratings GET /v2/catalog/works/{id}/tags # 想分页 / 想单独缓存时走这里 GET /v2/catalog/works/{id}/characters GET /v2/catalog/works/{id}/covers ``` 反过来,「这个厂牌名下有哪些作品」不是厂牌实体里的一个内嵌数组,而是作品集合上的一个过滤器 `?company_id=` ——这样它自带分页和全套过滤条件。角色、系列、引擎、标签同理。 ## 身份:目录 id 与外部 ref 每个实体有一个目录 id(十进制字符串),同时带一组 `refs` —— 它在各个上游的 id,形如 `source:external_id`。手里已经有 VNDB / Bangumi / DLsite / ErogameScape 的 id 时,直接反查即可,不必先按标题搜: ```http GET /v2/catalog/works?refs=vndb:v19658,bangumi:302835 ``` > [!WARNING] > `refs` 是**身份锚**,不保证是一个可以打开的网页。比如署名的 VNDB ref 是一个 staff alias id,它自己没有页面。可点开的地址一律在 `links` 块里。 ## 合并与重定向 同一个东西在上游被登记了两遍时,目录会把两行合并。被合并掉的那个 id **不会 301**,而是 `404` 加 `code: ENTITY_MERGED`,body 里给出 `object` 与 `current_id`,同时回一个 `Link rel=canonical`。 之所以不是重定向:静默跟随会让你以为自己拿到的还是原来那条记录,本地库里的旧 id 也永远不会被修正。要批量对账,读 `/v2/catalog/redirects` —— 它是一条按合并时间排序的游标 feed,每行给出 `old_id → current_id`。 ## 内容分级与 nsfw 闸 - `content_rating` 是作品的**事实分级**:`all_ages` / `sensitive` / `r18`。 - `nsfw=` 是**调用方自己控制的闸**:缺省隐藏 r18,显式 `nsfw=true` 才可见。只认 `true` / `false`,写别的是 `400` 而不是按默认值处理。 - `content_limit`(`sfw` / `nsfw`)是**编辑展示轴**,属于认领方站点的编辑判定,不等于事实分级。 这三个是正交的:一部 `all_ages` 的作品可以被某个站点标成 `nsfw` 展示轴,一部 `r18` 作品在 `nsfw=false` 下会整条消失而不是发个空壳。 ## 认领 生态里的站点可以「认领」一部作品——声明这一条对应它自己库里的哪个条目。已认领作品的 `claim` 块给出 `site`、`site_work_id`、`state` 与 `content_limit`;未认领的作品 `claim` 是 `null`(这个键**永远不会消失**)。 `state` 里的 `live` 与 `draft` 是公开的;`pending` / `declined` / `hidden` 是各站自己的审核队列,需要持有 `claim_events:read` 的密钥并用 `site=` 指名自家站点才看得到。 ## 编辑是公开的 目录的每一次修改都留痕,而且这份痕迹是公开只读的:`/v2/catalog/proposals` 是编辑提案,`/v2/catalog/revisions` 是修订历史。谁改了什么、被谁通过或拒绝,任何持密钥的应用都能读到。 - [全链走查](/docs/example) — 用两个真实系列把上面这些概念跑一遍。 - [词表](/docs/vocabularies) — 每个枚举的成员,以及它是开放还是封闭的。 - [增量镜像目录](/docs/mirror) — 把这份记录同步进自己的库,并保持新鲜。 --- # API 设计原则 v2 的形状不是从 v1 翻过来的,是重画的。这一页把重画时定下的规矩写在外面——不写下来,「加法演进」就只是我们单方面的假设。 > [!TIP] > **正式公开(2026-08-25)。** `/v2` 对所有开发者开放,在门户自助铸造 `nmk_` 密钥即可调用,不需要申请。已发布的形状按 additive-only 演进,删除与改名由 CI 的 oasdiff 破坏性变更门拦下。 ## 为什么是一次性切换 v1 是长出来的:先有一个站要用的接口,再有第二个站要用的接口,通用信封、数字错误码、五种分页风格就是这么攒起来的。它没坏,但它的每一个坏形状都会被复制到下一个接入方身上。 v2 是在**还没有第三方接入**的窗口里做的一次性破坏迁移:把所有已知的坏设计一次揪出来根治,而不是留一堆兼容开关慢慢腐烂。定调的那句话是「彻底干净」压倒「更兼容」——凡是在两者之间二选一,一律选干净,代价记在迁移账上。所以 v1 的五个面在 2026-08-27 连同它们的代码一起退役了,那些路径现在一律 `410`,`Link` 指向 `/v2`。 ## 十四条公理 每一条都是可执行的,不是口号——括号里是它在这份文档里的落点。 ### 协议层 - **资源即响应体。** 一次成功的读,响应体就是那个资源;一次成功的集合读,响应体就是那个集合。没有包住它们的通用外壳,没有「先解包再用」这一步。([请求与响应](/docs/conventions)) - **错误是一等类型,不是成功响应的变体。** 走 RFC 9457 `application/problem+json`,有自己的 media type 和自己的 schema。成功与失败在类型系统里可判别,不靠读一个字段的值。([错误处理](/docs/errors)) - **元信息属于协议层。** 分页、缓存、限流、退役、请求关联——凡 HTTP 已经有位置的,一律放响应头,不进 body。body 里只有领域数据。([缓存与条件请求](/docs/caching)) - **每个凭证体系一个前缀。** 一条请求只带一个凭证。需要两种身份同时在场的操作,说明它被放错了面。([鉴权与凭据](/docs/authentication)) ### 表示层 - **一个概念一个名字,一个名字一个概念。** 同一个概念在任何面上叫同一个名字、是同一个类型;同一个名字在任何面上指同一个概念。 - **不发不可达的字段,不发无法兑现的承诺。** 一个字段进契约的前提是它现在能有值,或者它的 `null` 有明确定义的语义。我们不会为了「看起来像现代 API」而发自己并不具备的东西——计费用量、分布式 trace、sandbox 标志都不在响应里。 - **拒绝,不降级;缺席,不含糊。** 非法输入是错误,不是「按默认值处理」。能力不足是拒绝,不是「给你能看的那部分」。一个键缺席只能有一种解释。 - **默认瘦,按需胖,且每一档都是静态 schema。** 默认响应只含身份内核,更多数据由 `view=` / `include=` 显式索取,每一档对应一个能写死在 spec 里、能被缓存、能被代码生成器建模的形状。([字段裁剪与批量读](/docs/shaping)) ### 可寻址性 - **每个被发布出去的 id 都必须有地址。** 响应里出现 `xxx_id`,就必须存在 `GET /v2/…/xxx/{id}`。没有地址的 id 是死信息。 - **每个实体族都必须有批量读。** 任何可寻址的族都支持 `?ids=`(≤100)。这是消灭 fan-out 的唯一结构,也是 agent 与镜像消费者价值最高的那个面。([集合与分页](/docs/pagination)) - **一个 URL 一个表示。** 公开读面的 200 响应体是请求 URL 的纯函数——同一个 URL,任何合法凭证拿到的字节相同。凭证只决定 200 还是 4xx,不决定字段集。 - **词表可发现。** 开放枚举的取值必须能通过 API 自己取到。agent 遇到没见过的值,应当有地方去查而不是去猜。([词表](/docs/vocabularies)) ### 文本与契约 - **面向机器的文本一律英文。** `title`、`code`、`detail`、枚举值、字段名全部英文且稳定。我们不做 `Accept-Language` 协商——本地化是消费端的事,我们提供的是稳定的机读键。 - **契约由运行中的代码产出,并被真实请求验证。** spec 不是第二份手写副本:它从真实路由生成,再由测试拿它去打那些路由。没有这一步,等于没有契约。 ## 客户端契约(必须) 这三条是双向的:我们承诺只做加法,前提是你的客户端按下面三条写。做不到的话,我们加一个字段就会打断你。 1. 客户端**必须忽略**响应中未知的字段。 2. 客户端**必须容忍**开放词表中未见过的取值。 3. 客户端**必须**为未知的错误 `code` 准备一个按 HTTP `status` 的兜底分支。 ## 我们承诺不会做的事 | 不会出现 | 因为 | | ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | | 通用响应信封(`{code,message,data}` 及其 `success` / `status` / `timestamp` 变体) | 它给每条响应加一层解包,给每个 schema 加一个「data 可能不存在」的分支 | | 数字业务错误码 | 数字空间会分叉、会撞、不可读、不能自解释 | | JSON number 类型的实体 id | JS `Number` 在 2^53 以上失真,一旦发出去就再也收不回来 | | 整数编码的序数枚举(分级、剧透档) | 招来算术与大小比较,而在既有阶梯中间插一档就是破坏性变更 | | 裸 content hash,或要求你自己拼 URL 的字段 | 图床搬家时一半引用坏掉、一半自愈,而 CDN base 从来不是公开契约的一部分 | | 静默忽略的 token(未知 `include` / `fields` / `sort` / facet 名) | 200 + 缺块 + 零信号 = 你会把它读成「数据里没有」 | | 静默降级的参数解析(`nsfw=on` 当成 false、`limit=5oo` 当成 100) | 被降级过的调用方会把窄结果读作全部真相 | | `next_cursor: null`,或与之并存的 `has_more` | 两个真值来源必然不同步;末页应当**省略**游标 | | 同一个 URL 按凭证档位返回不同的字段集 | 边缘缓存永远开不了——缓存住哪一份都是错的 | | `additionalProperties: false` | 与加法演进直接矛盾:我们加一个字段就打断严格客户端 | | 数组或 map 序列化成 `null` | 你要为永不触发的分支写判空,而真会 null 的那个反而没被声明 | | 纯读操作用 POST,或 `/lookup`、`/batch` 这类动词路径 | 不可缓存、不可条件请求,而资源 + 标准方法足以表达全部现有语义 | ## 这些承诺怎么被守住 - `/v2/catalog/openapi.json` 由运行中的路由生成,不是手写的第二份副本。 - 一组 CI 门在每次改动上跑:破坏性变更(oasdiff)、错误码注册表互斥、词表封闭标注、每个字段的 `description` 非空、声明过的状态码齐全、没有无约束的 `type: string`。 - 「同一 URL、不同密钥、字节相同」是拿真实生产请求验的,不是靠代码审查断言的。 演进规则、什么算破坏性变更、以及退役怎么通知,见 [版本与演进](/docs/versioning)。 --- # 请求与响应 所有端点共享的线格式。读完这一页,端点参考里的每个响应都能直接看懂。 ## 基础 | 项 | 值 | | ------------ | ----------------------------------------------------------- | | Base URL | `https://api.nextmoe.dev` | | 协议 | HTTPS only | | 请求体 | `application/json`(仅写操作) | | 成功响应 | `application/json` | | 错误响应 | `application/problem+json` | | 字符集 | UTF-8 | | 机器可读契约 | `https://api.nextmoe.dev/v2/catalog/openapi.json`(免密钥) | ## 没有信封 一次成功的读,响应体**就是**那个资源;一次成功的集合读,响应体就是那个集合。没有 `{code, message, data}`,没有 `success`,没有 `timestamp`。成功与否只看 HTTP 状态码。 ```json // 详情 { "object": "work", "id": "207379", "display_name": "…", … } // 集合 { "object": "list", "items": [ … ], "next_cursor": "cur_…" } ``` ## `object` 是类型判别符 每个资源、每个集合、每个内嵌子对象都带一个 `object`,值就是它的族名(`work` / `character` / `list` / `problem` …)。它是封闭词表,可以直接 switch。反过来,除它之外的任何字段都不要当判别符用——spec 里凡是写着 “Must not be used as a discriminant” 的字段,都是明确不承诺可判别的。 ## id 是字符串 所有实体 id 都是**十进制字符串**(`"207379"`),不是 JSON number。它在库里是 int64,超出 JavaScript `Number` 的安全整数范围就会静默失真,而一旦发出去这个决定就再也收不回来。 字符串 id **不带类型前缀**——`work` 的 id 就是 `"207379"`,不是 `"work_207379"`。类型信息在 `object` 里,不重复编码进 id。 ## 时间与日期 - 时间戳是 RFC 3339 UTC:`"2026-08-29T02:51:07Z"`。 - 日历日期是 `YYYY-MM-DD`。 - 发售日精度单独用一个字段表达:`release_date_precision` 取 `day` / `month` / `year`。月精度的日期落在当月 1 号,年精度落在 1 月 1 日——**不要**把它当成真的发生在那一天。 ## null 的语义 - **数组与 map 永不 `null`。** 没有内容就是 `[]` 或 `{}`。 - **未记录的标量是 `null`,不是缺键。** 例如 `latin: null` 表示这条没有罗马字,而不是「这个字段不存在」。 - **默认瘦身时整块不出现。** `include=` 没点名的块根本不在响应里——这和「块存在但是空的」是两回事:前者是你没要,后者是真没有。 - 零值是合法值。`0` 票、`false`、`""` 都会照发,不会被当成「没有」抹掉。 > [!NOTE] > 一个键的缺席只有一种解释。如果你发现某个键的消失可以有两种读法,那是缺陷,请报给我们。 ## 参数解析:拒绝,不降级 | 写法 | 结果 | | ------------------------------- | --------------------------------------- | | `nsfw=true` / `nsfw=false` | 生效 | | `nsfw=1`、`nsfw=on`、`nsfw=yes` | `400 INVALID_PARAMETER`——不会当成 false | | `limit=101` | `400 LIMIT_TOO_LARGE`——不会截断成 100 | | `include=tags,nope` | `400 UNKNOWN_INCLUDE`——不会只给 tags | | `sort=whatever` | `400 UNKNOWN_SORT` | | `ids=` 超过 100 个 | `400 TOO_MANY_IDS` | 多值参数一律用英文逗号分隔,不用重复的 `key=a&key=b`。被降级过的调用方会把窄结果读成全部真相,所以这里宁可报错。 ## 响应头 | 头 | 说明 | | -------------------------------- | ---------------------------------------------------------------------------------- | | `ETag` | 每个 200 的 GET 都有。配 `If-None-Match` 拿 304,见 [缓存](/docs/caching) | | `Cache-Control` | 公开注册表面可缓存,其余 `private, no-store` | | `Vary` | `Authorization, Accept-Encoding` | | `RateLimit` / `RateLimit-Policy` | 当前窗口余量与策略,见 [限流](/docs/rate-limits) | | `Retry-After` | 429 时给出等待秒数 | | `X-Request-ID` | `req_` + 26 位 ULID。错误体里的 `request_id` 是同一个值——报问题时请带上它 | | `Link` | 至少带 `rel="service-desc"` 指向本门户;`ENTITY_MERGED` 时额外带 `rel="canonical"` | | `Deprecation` / `Sunset` | 端点进入退役期时出现,见 [版本与演进](/docs/versioning) | ## 跨域 v2 允许任意 origin,并暴露 `ETag`、`Link`、`RateLimit*`、`Retry-After`、`Deprecation`、`Sunset`、`X-Request-ID`。 > [!CAUTION] > **能跨域不等于该在浏览器里调。** 应用密钥是机密,放进前端等于公开发布它。浏览器要用数据,请让自己的后端代理;要代表用户读写,用用户访问令牌。 ## 写操作 - 请求体是 JSON,`Content-Type: application/json`。 - `POST` 支持 `Idempotency-Key`:同一把密钥、同一路径、同一个 key 的重复请求会重放首次结果(保存 24 小时);body 不同则是 `409 IDEMPOTENCY_KEY_REUSED`。 - 改动别人也能改的资源时要求乐观并发:把读到的 `ETag` 放进 `If-Match`。不带是 `428 PRECONDITION_REQUIRED`,带了但对不上是 `412 PRECONDITION_FAILED`。目前 `PATCH /v2/me/claims/{id}`、`PATCH /v2/me/proposals/{id}`、`POST /v2/me/proposals/{id}/amendments` 与两个 `moderation` 决策面都要求它。 写面的完整清单与流程见 [接入用户数据](/docs/user-data)。 --- # 集合与分页 整个 API 只有一种分页:keyset 游标。所有集合共用同一个信封、同一套参数、同一条「翻到头了」的判据。 ## list 信封 ```json { "object": "list", "items": [ … ], "next_cursor": "cur_…", // 末页省略 "total": 12345, // 只在 include_total=true 时出现 "facets": { … }, // 只在 facets= 请求时出现 "missing": ["…"] // 只在 ids= / refs= 批量车道出现 } ``` `object` 和 `items` 恒在。`items` 是空数组时就是 `[]`,永远不会是 `null`。 ## 游标 - `next_cursor` 是**不透明串**,以 `cur_` 开头。原样回传即可,不要解析、不要构造、不要基于它做算术。 - **末页直接不出现这个键。** 没有 `next_cursor: null`,也没有 `has_more`——两个真值来源必然会不同步。 - 不要用 `items.length === limit` 判断还有没有下一页:满页末页必然说谎。 - 游标是 keyset 而不是 offset,所以深翻页不会越翻越慢,也不会因为中途有新行插入而重复或漏行。 正确的翻页循环长这样: ```javascript let cursor do { const url = new URL('https://api.nextmoe.dev/v2/catalog/works') url.searchParams.set('limit', '100') if (cursor) url.searchParams.set('cursor', cursor) const page = await fetch(url, { headers: { Authorization: `Bearer ${key}` } }).then((r) => r.json()) for (const work of page.items) handle(work) cursor = page.next_cursor // 末页是 undefined,循环自然结束 } while (cursor) ``` ## limit - 范围 1–100,默认 20。 - `limit=101` 是 `400 LIMIT_TOO_LARGE`,**不会**被截断成 100。静默截断会让你以为自己拿到了全部。 ## total 默认不发 要精确总数就传 `include_total=true`。它默认关闭是有代价考量的:带过滤条件的精确 `COUNT` 是对同一批数据的第二次全扫,最先在压力下超时,而且它和你刚拿到的那一页天然不一致(两次查询之间数据会变)。 做「共 N 页」的分页器请三思——这个 API 的分页是游标式的,页码本来就没有稳定含义。做「加载更多」会更贴合。 ## sort 每个集合声明自己的一套封闭 `sort` 键,未知值是 `400 UNKNOWN_SORT`。排序一律带 tie-breaker——平局顺序不会由存储内部决定,所以重建索引不会打乱翻页。 以作品集合为例,它接受 `id`(默认)、`updated`、`relevance`、`released_desc`、`released_asc`、`popularity`。带 `q=` 做标题搜索时会切到搜索索引,此时只有 `relevance`、`released_desc`、`released_asc`、`popularity` 有意义。 ## facets `facets=` 请求分面计数,结果放在 `facets` 里,未知的分面名是 `400 UNKNOWN_FACET`。作品集合支持 `tag_id`、`company_id`、`olang`、`content_rating`、`medium`、`platform`。 ```http GET /v2/catalog/works?facets=content_rating,medium&limit=1 ``` ## 批量车道 `ids=` / `refs=` 是**另一条车道**:一次最多 100 个,没有分页,`next_cursor` 不会出现。请求了但不可见的 id 原样回在 `missing[]` 里,而不是让整个请求 404。详见 [字段裁剪与批量读](/docs/shaping)。 > [!NOTE] > 需要把整个目录同步下来时,不要靠翻 `/v2/catalog/works` 硬扫——用 [`/v2/catalog/changes` 镜像信道](/docs/mirror),它按更新时间升序枚举整个人口,冷启动一次翻完,之后只要增量。 --- # 字段裁剪与批量读 默认瘦,按需胖。三个参数决定你拿到什么形状,另外两个决定你一次拿几条——用好它们,多数集成不需要 fan-out。 ## 三档叠加 三个参数按固定顺序作用,每一档都是一个能写死在 spec 里的静态形状: | 参数 | 做什么 | 未知取值 | | ---------- | ------------------------------------ | ------------------------ | | `view=` | 选一档预设:`basic`(默认)或 `full` | `400 UNKNOWN_ENUM_VALUE` | | `include=` | 点名要哪些块(逗号分隔) | `400 UNKNOWN_INCLUDE` | | `fields=` | 在上面的结果上做顶层键投影 | `400 UNKNOWN_FIELD` | ```text view=full → 展开该族 full_set 里的块 + include= → 再并上你点名的块 + fields= → 最后只保留你要的顶层键(object 与 id 恒留) ``` > [!WARNING] > 三者都不会静默忽略未知 token。`include=tags,nope` 是 `400`,不是「给你 tags 就算了」——200 + 缺块 + 零信号会被读成「数据里没有」,那是最贵的一种误解。 ## view `basic` 是身份内核:足够渲染一张卡片、做一次去重、建一条外链。`full` 展开这个族的「常用全集」,但**不是所有块**——像 `credits`(署名表)这种量大且属于明确意图的块不在 `full` 里,必须点名。 ## include 每个族有自己的 include 词表,而且**列表面和详情面的词表不一样**——列表是批量水合的,只有能批量取的块才在列表词表里;只有逐条才有形状的块留在详情面和子资源面上。 | 面 | 可用 token | | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `GET /v2/catalog/works/{id}`(详情,18 个) | `titles` `refs` `relations` `credits` `releases` `popularity` `ratings` `tags` `playtimes` `series` `platforms` `intros` `covers` `screenshots` `characters` `companies` `engines` `links` | | `GET /v2/catalog/works`(列表,8 个) | `titles` `refs` `intros` `covers` `companies` `ratings` `tags` `credits` | 不用记——发现面会告诉你,而且它和路由是同一份真相: ```bash curl "https://api.nextmoe.dev/v2/catalog/schemas/work" { "object": "object_schema", "target_object": "work", "include": [ … ], // 详情面吃的 token "full_set": [ … ], // view=full 在详情面展开的子集 "list_include": [ … ], // 集合面吃的 token "list_full_set": [ … ], "fields": [ … ], // 可编辑字段 "creation_disabled": false } ``` 列表面拿不到的块,走作品的子资源面单独读:`works/{id}/characters`、`works/{id}/screenshots`、`works/{id}/relations` 等等——它们自带分页,也能单独缓存。 ## fields `fields=` 在 `view` / `include` 之后做一次顶层键投影,用来把响应压到你真正要的那几个键。`object` 与 `id` 永远保留(否则结果就无法判别、无法回指)。 ```http GET /v2/catalog/works?fields=display_name,content_rating&limit=100 { "object": "list", "items": [ { "object": "work", "id": "207379", "display_name": "…", "content_rating": "all_ages" } ] } ``` 投影发生在 `ETag` 计算之前,所以裁剪过的响应有它自己的验证器,条件请求照常工作。 ## 批量读:ids 与 refs 这是消灭 fan-out 的那个结构。任何可寻址的族都支持批量车道,一次最多 **100** 个: | 参数 | 取什么 | 例子 | | ------- | ---------------------------- | --------------------------------------------------- | | `ids=` | 目录 id | `ids=207379,207380,207381` | | `refs=` | 外部锚,`source:external_id` | `refs=vndb:v19658,bangumi:302835,dlsite:RJ01012345` | - 批量车道**没有分页**:`next_cursor` 不会出现,`limit` 不参与。 - 超过 100 个是 `400 TOO_MANY_IDS`——自己分片,不要指望被截断。 - 请求了但不可见(不存在、被 nsfw 闸挡住、已合并)的键原样回在 `missing[]` 里。整个请求不会因为其中一个不存在就 404。 - `view` / `include` / `fields` 在批量车道上照常生效,所以「100 部作品各带标签和封面」是一次请求。 ```http GET /v2/catalog/works?refs=vndb:v19658,vndb:v99999&include=covers { "object": "list", "items": [ { "object": "work", "id": "207379", … } ], "missing": ["vndb:v99999"] } ``` > [!TIP] > 把「先搜索、再对每条命中打一次详情」换成「一次集合读 + 一次 `ids=` 批量水合」,请求数从 N+1 掉到 2。这是限流下最划算的一次改写。 --- # 错误处理 错误是一等资源类型,不是成功响应的一个变体。它有自己的 media type、自己的 schema、自己的稳定标识,和成功响应在类型系统里可判别。 ## 形状 所有 4xx / 5xx 响应都是 RFC 9457 `application/problem+json`: ```http HTTP/1.1 400 Bad Request Content-Type: application/problem+json X-Request-ID: req_01JBQ7X4M2K9P3W5T8ZVN6HRDC { "type": "https://developer.nextmoe.dev/problems/platform/unknown-include", "title": "Unknown include", "status": 400, "code": "UNKNOWN_INCLUDE", "detail": "include= received a token that is not in this collection's vocabulary: nope", "instance": "/v2/catalog/works?include=tags,nope", "request_id": "req_01JBQ7X4M2K9P3W5T8ZVN6HRDC", "errors": [ { "parameter": "include", "reason": "UNKNOWN_VALUE", "detail": "nope" } ] } ``` | 字段 | 恒在 | 说明 | | ----------------------- | ---- | ---------------------------------------------------------------- | | `type` | 是 | 稳定的 problem type URI,解析到 [本站的错误码页](/problems) | | `title` | 是 | 这个类型的固定英文短语,不随请求变化 | | `status` | 是 | 与状态行一致的 HTTP 状态码 | | `code` | 是 | **顶层稳定标识**,封闭注册表里的 UPPER_SNAKE 字符串 | | `detail` | 是 | 英文、随请求变化的说明。没什么可补充时是空串。**不要拿它做分支** | | `instance` | 是 | 出错的路径与查询串 | | `request_id` | 是 | `req_` + 26 位 ULID,与 `X-Request-ID` 同值。报问题时请带上 | | `errors` | 是 | 字段级失败明细。不是字段级错误时是 `[]`,**永远不是 null** | | `object` / `current_id` | 否 | 仅 `ENTITY_MERGED` 时出现 | ## 两层标识,两个注册表 顶层用 `code`,`errors[]` 里用 `reason`——**故意不同名**。两个层级用同一个键名,会让「读错嵌套层」这个最常见的消费端 bug 拿到一个看起来合理的值,而不是 `undefined`。两份注册表的成员集合互不重叠。 - `code`:这次请求整体为什么失败。例如 `UNKNOWN_INCLUDE`、`SCOPE_REQUIRED`、`RATE_LIMITED`。 - `reason`:某个具体字段为什么不合格。封闭的一小套:`REQUIRED`、`INVALID_FORMAT`、`OUT_OF_RANGE`、`TOO_LONG`、`TOO_SHORT`、`TOO_MANY_ITEMS`、`DUPLICATE_ITEM`、`UNKNOWN_VALUE`、`NOT_ALLOWED_VALUE`、`UNKNOWN_REFERENCE`、`IMMUTABLE`、`INCONSISTENT_WITH`。 `errors[]` 的每一项**恰好**带 `pointer`、`parameter`、`header` 三者之一,指出出问题的位置:`pointer` 是请求体里的 JSON Pointer(RFC 6901),`parameter` 是查询或路径参数名,`header` 是请求头名。 ## 怎么分支 1. **先看 HTTP `status`。** 这是唯一保证你永远认得的东西。 2. **再看 `code`。** 认得就走专门分支。 3. **认不得就按 `status` 兜底。** 这是[客户端契约](/docs/design#client-contract)的第三条:我们会往注册表里加新的 `code`,你的客户端不能因此崩掉。 不要拿 `detail` 或 `title` 做分支——`detail` 随请求变化,两者都是英文散文。 ## 按状态码的处置 | 状态 | 典型 `code` | 该怎么办 | | ------------- | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------ | | `400` | `INVALID_PARAMETER` `UNKNOWN_INCLUDE` `UNKNOWN_SORT` `LIMIT_TOO_LARGE` `TOO_MANY_IDS` `INVALID_CURSOR` | 你的请求有 bug。**不要重试**,修代码 | | `401` | `MISSING_CREDENTIAL` `INVALID_CREDENTIAL` | 带上凭据;用户令牌则刷新一次再试 | | `403` | `SCOPE_REQUIRED` `USER_IDENTITY_REQUIRED` `PERMISSION_REQUIRED` `TENANT_MISMATCH` | 换凭据或补 scope。重试同一请求没有意义 | | `404` | `NOT_FOUND` `ENTITY_MERGED` | `ENTITY_MERGED` 时读 `current_id` 重指本地行;其余当作不存在 | | `405` | `METHOD_NOT_ALLOWED` | 路径在,方法不在 | | `409` | `ALREADY_EXISTS` `IDEMPOTENCY_KEY_REUSED` `INVALID_STATE_TRANSITION` | 先读当前状态再决定,不要盲目重试 | | `410` | `GONE` | 这个 URL 永久退役了。v1 的六个前缀都在这里 | | `412` / `428` | `PRECONDITION_FAILED` / `PRECONDITION_REQUIRED` | 重新 GET 拿最新 `ETag`,带上 `If-Match` 再写 | | `429` | `RATE_LIMITED` `QUOTA_EXCEEDED` | 按 `Retry-After` 等待。见 [限流与配额](/docs/rate-limits) | | `5xx` | `INTERNAL_ERROR` `SERVICE_UNAVAILABLE` | 指数退避 + 抖动重试;连续失败就降级到本地缓存 | ## ENTITY_MERGED 被合并掉的实体不 301。请求它会拿到 `404` + `code: ENTITY_MERGED`,body 里带 `object` 与 `current_id`,响应头带 `Link rel="canonical"`: ```json { "type": "https://developer.nextmoe.dev/problems/catalog/entity-merged", "status": 404, "code": "ENTITY_MERGED", "object": "work", "current_id": "207379", … } ``` 之所以不是重定向:静默跟随会让你以为拿到的还是原来那条记录,本地库里的旧 id 也永远不会被修正。批量对账读 [`/v2/catalog/redirects`](/docs/mirror#merges)。 ## 完整注册表 每个 `type` URI 都能在本站打开,写着它的状态码、含义和触发条件。 - [错误码](/problems) — 按域分组的全部错误码,中英对照,每个码一页。 - [限流与配额](/docs/rate-limits) — 429 的两种成因,以及正确的退避方式。 --- # 限流与配额 调用与编辑都完全免费,没有付费档位——只有一层防滥用的限流。两个窗口:每分钟的速率,和每天的配额。 ## 两个窗口 | 档位 | 每分钟 | 每天 | 怎么拿到 | | ---------------- | ------ | --------- | -------------------------- | | `free` | 60 | 50,000 | 自助铸的密钥默认就是这一档 | | `trusted` | 600 | 1,000,000 | 联系我们,说明用途与量级 | | `internal` | 不限 | 不限 | 生态内部服务 | | 匿名(不带密钥) | 100 | 10,000 | 按来源 IP 计 | 两个窗口独立计数:分钟窗按自然分钟切,日配额在 UTC 零点归零。先撞哪个就先返回哪个。 ## 按谁计数 | 你带的凭据 | 计数桶 | | ------------ | -------------------------------------------------- | | 应用密钥 | 密钥所属的**应用**(同一应用的多把密钥共用一个桶) | | 用户访问令牌 | 那个**用户** | | 什么都不带 | 来源 IP | > [!WARNING] > 把整个用户群从一个服务端出口代理出去时,请务必用用户令牌调用 `/v2/me`。否则所有人会落进同一个匿名 IP 桶——这正是曾经真实发生过的一次事故:一个站点的全部用户共享一个匿名桶,每天一到量就集体 429。 ## 读响应头 每个 `/v2` 响应都带当前窗口的余量,不需要靠撞墙来发现自己快到顶了: ```http RateLimit: limit=60, remaining=57, reset=23 RateLimit-Policy: 60;w=60, 50000;w=86400 X-RateLimit-Limit: 60 X-RateLimit-Remaining: 57 X-RateLimit-Reset: 23 ``` - `RateLimit` 是 IETF 草案格式:`limit` 是本窗口上限,`remaining` 是剩余次数,`reset` 是**秒数**(不是时间戳)。 - `RateLimit-Policy` 同时声明两个窗口:`60;w=60` 是每分钟 60 次,`50000;w=86400` 是每天 50,000 次。 - `X-RateLimit-*` 是给旧客户端库的同值镜像,三个头都在 CORS 的 expose 列表里。 ## 撞上限之后 | `code` | 含义 | `Retry-After` | | ---------------- | ---------------- | ----------------------------- | | `RATE_LIMITED` | 分钟窗打满了 | 到本分钟结束的秒数(最多 60) | | `QUOTA_EXCEEDED` | 今天的配额用完了 | 到 UTC 零点的秒数 | 1. **先读 `Retry-After`**,按它等待。不要立刻重试——重试本身也会计数。 2. 退避要带抖动。整点齐发的定时任务会在同一秒一起撞墙,然后在同一秒一起重试。 3. `QUOTA_EXCEEDED` 不要退避重试。今天没有额度了,等待没有意义——该做的是把当天的调用量降下来。 ## 把量降下来的四个办法 - **批量读代替 fan-out。** `ids=` / `refs=` 一次 100 条,把 N+1 压成 2 次请求。见 [字段裁剪与批量读](/docs/shaping#batch)。 - **用条件请求。** 带 `If-None-Match` 拿到的 `304` 同样计数,但省掉了传输与解析;真正省调用的是把 `ETag` 缓存起来、在 TTL 内根本不发请求。见 [缓存](/docs/caching)。 - **别轮询全量。** 需要跟住变化就订 [`/v2/catalog/changes`](/docs/mirror) 增量信道,不要每晚重扫一遍目录。 - **只要你要的字段。** `fields=` 不省调用次数,但能显著减少你自己这边的解析与存储开销。 这些都做了还是不够,就来谈 `trusted` 档——描述清楚场景与量级即可,我们更愿意给档位,而不是看着你写重试风暴。 --- # 缓存与条件请求 每个 200 的 GET 都带 `ETag`。多数面按 `private, no-store` 声明——这不是保守,是因为它们的 body 会随凭据能力位变化,缓存住哪一份都是错的。 ## ETag 与 304 所有 200 的 GET 都带一个强 `ETag`,它是响应体的哈希——`fields=` 裁剪过的响应有它自己的验证器,所以条件请求在任何形状上都成立。 ```bash # 第一次 curl -i "https://api.nextmoe.dev/v2/catalog/works/207379" -H "Authorization: Bearer nmk_live_…" < HTTP/1.1 200 OK < ETag: "9f2a1c…" # 之后带上它 curl -i "https://api.nextmoe.dev/v2/catalog/works/207379" \ -H "Authorization: Bearer nmk_live_…" \ -H 'If-None-Match: "9f2a1c…"' < HTTP/1.1 304 Not Modified ``` > [!NOTE] > `304` 同样计入限流。它省的是带宽和解析,不是调用次数——真正省调用的是把响应连同 `ETag` 存下来,在你自己的 TTL 内根本不发这个请求。 ## Cache-Control | 面 | 声明 | | --------------------------------------------------------------------------------------- | ----------------------------------------------------------------- | | `/v2/problems`、`/v2/vocabularies`、`/v2/catalog/schemas/*`、`/v2/catalog/openapi.json` | `public, max-age=300, s-maxage=1800, stale-while-revalidate=3600` | | 其余所有 2xx | `private, no-store` | | 所有 4xx / 5xx | `no-store` | 每个响应还带 `Vary: Authorization, Accept-Encoding`。 ## 为什么可共享缓存的只有注册表面 `public` 的前提是「同一个 URL,任何人拿到的字节相同」。注册表面满足这一点:错误码、词表、对象 schema、OpenAPI 文档不随谁在问而变化。 `/v2/catalog/*` 不满足。它是密钥门内的面,body 会随认领站点的围栏、nsfw 能力位、`claim_state` 可见范围而变——一个中间层如果自作主张把它当公共内容缓存下来,就会把 A 的视图发给 B。所以这里是**默认拒绝**:只有明确证明了「body 与凭据无关」的面才进 public 名单,其余一律 `private, no-store`。 > [!WARNING] > `Vary: Authorization` 救不了这件事——这是实测过的:曾经有边缘节点在缓存里放了一份 `max-age=14400` 的 `/v2/catalog` 响应,而这个源站从来没有设过这个值。所以现在每一条 2xx 都显式声明自己的缓存意图,不给中间层留下「自己发明一个」的空间。 你自己的服务端当然可以缓存这些响应——你知道自己的密钥、也知道自己的可见范围。不要做的是把它们交给一个不理解这些围栏的共享中间层。 ## 写操作的条件请求 改动别人也能改的资源时用乐观并发:先 GET 拿 `ETag`,再把它放进 `If-Match` 写回去。 ```bash curl -X PATCH "https://api.nextmoe.dev/v2/me/proposals/8812" \ -H "Authorization: Bearer " \ -H 'If-Match: "9f2a1c…"' \ -H "Content-Type: application/json" \ -d '{"note":"补充一条来源"}' ``` - 不带 `If-Match` 是 `428 PRECONDITION_REQUIRED`。 - 带了但对不上是 `412 PRECONDITION_FAILED`——说明有人在你读之后改过,重新 GET 再合并。 - `If-Match: *` 只表示「存在即可」,它**不能**用来绕过任何权限检查。 ## 幂等键 `POST` 支持 `Idempotency-Key`。同一个计数身份、同一路径、同一个 key 的重复请求会重放首次的响应(保留 24 小时),网络超时后安全重试因此不会写两条。 ```bash curl -X POST "https://api.nextmoe.dev/v2/me/claims" \ -H "Authorization: Bearer " \ -H "Idempotency-Key: 6f3c2b7a-1d54-4a90-9d1e-2c7f8b0a5e31" \ -H "Content-Type: application/json" \ -d '{ … }' ``` 同一个 key 配上**不同的** body 是 `409 IDEMPOTENCY_KEY_REUSED`——这是在提醒你 key 生成有 bug,而不是在阻挠你。 ## 要跟住变化,不要轮询 把目录同步进自己的库,然后靠增量信道保持新鲜,比任何缓存策略都有效:`GET /v2/catalog/changes` 按 `(updated_at, id)` 升序枚举整个人口,冷启动翻一遍就是全量清点,之后只拉增量。配方见 [增量镜像目录](/docs/mirror)。 --- # 版本与演进 `/v2` 是唯一的公开面,此后只做加法。删除与改名过不去 CI 的破坏性变更门——真要破坏,只能升主版本。 ## 当前状态 | 面 | 状态 | | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | | `/v2` | **正式公开**(2026-08-25 GA),88 个端点,spec `2.3.0` | | `/v1/catalog`、`/v1/news`、`/v1/store`、`/v1/playtime`、`/api/v1/catalog`、`/api/v1/user/catalog` | **已退役**(2026-08-27)。一律 `410 Gone`,`Link` 指向 `/v2` | v1 是连同它的代码一起退役的,不是留一个转发层——这样就不会有人「暂时还能用」着用到明年。 ## 什么算破坏性变更 | 算破坏 | 不算破坏 | | ------------------------------------------ | ------------------------------- | | 删除一个字段、端点或参数 | 新增一个字段 | | 给字段或参数改名 | 新增一个端点 | | 收紧类型(可空变不可空、放宽的枚举变封闭) | 放宽约束(必填变可选) | | 给**封闭**词表加一个成员 | 给**开放**词表加一个取值 | | 改变一个已有取值的含义 | 往错误码注册表里加一个新 `code` | | 改变默认值 | 新增一个可选参数 | > [!NOTE] > 「给封闭词表加成员」算破坏,是因为封闭意味着我们承诺过成员集合就这些——你的 `switch` 可以没有 `default`。开放词表则相反:`x-vocabulary-closed: false` 就是在提前告诉你会有新值。所以[客户端契约](/docs/design#client-contract)要求你容忍开放词表里没见过的取值。 ## 这条线怎么守住 - `/v2/catalog/openapi.json` 由运行中的路由生成,不是手写的。 - 每次改动都会拿改动前后的两份 spec 跑 oasdiff 的破坏性变更检查;命中就不许合并。 - 一批契约门跟着跑:错误码注册表互斥、词表封闭标注齐全、每个字段 `description` 非空、声明过的状态码完整、没有无约束的 `type: string`。 - 门户这份文档、`llms.txt` 与每页的 Markdown 孪生,都从同一份 spec 生成——文档和契约不会各说各话。 ## 退役怎么通知 端点要退役时会先进入退役期,响应头带上 `Deprecation` 与 `Sunset`(两个头都在 CORS 的 expose 列表里,浏览器侧也读得到)。把它们接进监控——它们出现的那天,就是你还有时间从容迁移的那天。 退役生效后,那个路径返回 `410 Gone`,并指出接替它的面: ```http HTTP/1.1 410 Gone Link: ; rel="successor-version" Content-Type: application/problem+json { "code": "GONE", "status": 410, … } ``` 这就是 v1 六个前缀现在的样子。等到 `410` 才发现问题就只剩加急了,所以请让 `Deprecation` 触发告警,而不是等 `410` 触发工单。 ## 跟住变化 - `GET /v2/catalog/openapi.json` 免密钥,`info.version` 是当前 spec 版本。把它拉进 CI,diff 一下就知道这次动了什么。 - 本站每个文档页都有 Markdown 孪生(路由后加 `.md`),全部端点内联在 [`/llms-full.txt`](/llms-full.txt)——适合让 agent 定期通读一遍。 - `/v2/vocabularies` 与 `/v2/catalog/schemas/{object}` 是运行时的词表与形状发现面,比任何文档都新。 设计层面的承诺与「我们不会做的事」,见 [API 设计原则](/docs/design)。 --- # 全链走查 以 **SEQUEL**(同人 · リーフジオメトリ)与 **いろセカ**(商业 · FAVORITE)两个真实系列演示 搜索 → 详情 → 系列/厂牌深链 → 外部 id 反查,全部走公开 API v2。 两系列均为 R18——正好演示调用方自控的 `nsfw` 参数:缺省隐藏,显式 `nsfw=true` 才可见;只认 `true` / `false`,写别的是 `400` 而不是按默认值处理。 ## 1 · 标题搜索 ```bash curl "https://api.nextmoe.dev/v2/catalog/search?object=work&q=SEQUEL&nsfw=true" \ -H "Authorization: Bearer nmk_live_" ``` ```json { "object": "list", "items": [ { "object": "search_result", "target_object": "work", "id": "207379", "display_name": "この素晴らしい世界に祝福を!このゲーム性に嫉妬を!", "latin": "…", "localized": { "zh-Hans": { "value": "…", "is_machine": false } }, "sources": ["dlsite", "bangumi"], "content_rating": "r18" } ], "total": 5 } ``` 没有信封:响应体**就是**那个集合。id 一律是字符串,翻页用 `next_cursor`(末页直接不出现这个键,没有 `has_more`)。 ## 2 · 详情:默认瘦,按 include 取块 ```bash curl "https://api.nextmoe.dev/v2/catalog/works/207379?nsfw=true\ &include=series,companies,popularity,titles,relations,credits" \ -H "Authorization: Bearer nmk_live_" ``` ```json { "object": "work", "id": "207379", "medium": "galgame", "display_name": "…", "content_rating": "r18", "release_status": "released", "series": [ { "object": "series", "id": "320", "display_name": "SEQUEL", "member_count": 5, "source": "dlsite" } ], "companies": [ { "object": "company", "id": "11310", "display_name": "リーフジオメトリ", "company_kind": "doujin_circle", "attribution_role": "circle" } ], "popularity": [ { "source": "bangumi", "metric": "bgm_wish", "value": 33 }, { "source": "bangumi", "metric": "bgm_collect", "value": 29 }, { "source": "dlsite", "metric": "downloads", "value": 17710 }, { "source": "dlsite", "metric": "wishlist", "value": 17114 } ], "titles": [ { "lang": "ja", "title": "…", "latin": "…", "title_kind": "official", "is_machine": false } ], "relations": [ /* include=relations:关系边 + 对端 work(view=basic) */ ], "credits": [ /* include=credits:按 role_key 分组;CV 那条带 character_id */ ] } ``` 还有 `refs` / `intros` / `tags` / `covers` / `screenshots` / `releases` / `ratings` / `playtimes` / `characters` / `engines` / `links` / `platforms`——v2 默认瘦身,每一块都要在 `include=` 里点名,写错 token 是 `400 UNKNOWN_INCLUDE` 而不是静默少块。 多源数据在同一响应里并列出(按 `source` 区分,刻度保持源原生、绝不归一): ```json // いろセカ家族(w426 紅い瞳)的多源评分与时长(include=ratings,playtimes) "ratings": [ { "source": "vndb", "score": 7.87, "vote_count": 375 }, { "source": "bangumi", "score": 6.9, "vote_count": 1065, "rank": 4184 }, { "source": "erogamescape", "score": 78, "vote_count": 446 } ], "playtimes": [ { "source": "vndb", "minutes": 578, "vote_count": 38 }, { "source": "erogamescape", "minutes": 600 } ] ``` `rank` / `vote_count` 这类「没记录」的位置恒发 `null` 或 `0`,不会整键消失。 ## 3 · 厂牌 / 社团档案 ```bash curl "https://api.nextmoe.dev/v2/catalog/companies/11310" \ -H "Authorization: Bearer nmk_live_" ``` ```json { "object": "company", "id": "11310", "display_name": "リーフジオメトリ", "latin": "…", "localized": {}, "company_kind": "doujin_circle" } ``` `work_count`(同一 nsfw 闸下可见的作品数)恒在。名下作品**不内嵌**在这个实体里——反查是作品集合上的一个过滤器,这样它自带分页与全套过滤: ```bash curl "https://api.nextmoe.dev/v2/catalog/works?company_id=11310&nsfw=true" \ -H "Authorization: Bearer nmk_live_" ``` ## 4 · 外部 id 反查 手握 VNDB / Bangumi / DLsite / ErogameScape id 时的首选路径: ```bash curl "https://api.nextmoe.dev/v2/catalog/works?refs=vndb:v19658&nsfw=true" \ -H "Authorization: Bearer nmk_live_" ``` 批量就把最多 100 个 `source:external_id` 用逗号连起来一次问完——v2 没有 `/lookup` 这样的动词路径,反查是集合上的 `refs=` 参数。没锚到的会原样回在 `missing[]` 里,而不是让整个请求 404。 ## 接下来 - [端点参考](/docs/v2) — 全部 88 个端点的参数、响应与 curl。 - [字段裁剪与批量读](/docs/shaping) — `include` / `fields` / `ids` / `refs` 的完整规则。 - [数据浏览](/explore) — 先不写代码,在浏览器里点着试。 机器可读 spec 在 `https://api.nextmoe.dev/v2/catalog/openapi.json`(免密钥)。 --- # 增量镜像目录 把目录同步进自己的库,然后靠一条游标信道保持新鲜。冷启动一次全量,之后只拉变化——不需要夜间重扫,也不需要「列出全部 id」那种面。 > [!NOTE] > 先问自己需不需要镜像。只是按需查几条?直接读就好,配上 `ids=` 批量与 `ETag` 缓存足够了。**要在自己的 SQL 里过滤、排序、连表**,才值得镜像。 ## 信道 `GET /v2/catalog/changes` 按 `(updated_at, id)` **升序**枚举目录人口。每一项是这样: ```json { "object": "change", "target_object": "work", "id": "207379", "updated_at": "2026-08-29T02:51:07Z", "gone": true // 只在该 id 已离开公开人口时出现,从不发 false } ``` 它是一条普通的游标集合,`limit`(≤100)、`cursor`、`next_cursor` 与其它集合完全一样。 ## 承诺了什么 凡是改动作品的 **claim 状态**、**编辑展示轴**(`content_limit` 的两个输入:`display_nsfw` 与 `content_rating`)或**作品存在性**的写路径,都会 bump 这一行的 `updated_at`,因而必然在这条 feed 里现身。 其余字段(封面 / 标签 / 标题 / 简介 / 评分)是**尽力而为**——它们多数也会连带 touch 作品行,但只有上面三项是承诺。要对某个非承诺字段做强一致的镜像,请告诉我们,那说明它该被提升成承诺。 ## 冷启动 1. **从空游标翻这条 feed。** 它按「最旧更新优先」枚举整个人口,所以第一次翻完就是一次全量清点——不需要另一个「列出全部 id」的面。 2. 每页拿到的 id 以 **≤100 一批**打 `/v2/catalog/works?ids=`,把需要的块用 `include=` 一次水合。 3. **两道闸全开**:传 `nsfw=true`,并且**不要传** `content_limit`。否则被闸掉的作品会在本地留成空洞,而你不会收到任何信号。 4. 存下最后一页的 `next_cursor`。 ```http GET /v2/catalog/changes?limit=100 GET /v2/catalog/works?ids=207379,207380,…&nsfw=true&include=covers,titles,companies ``` ## 稳态 1. 带着存下的游标按自己的节奏轮询。游标是不透明串,原样回传。 2. 每页的 id 同样以 ≤100 一批水合,覆盖本地行。 3. 把新的 `next_cursor` 存回去。末页不带这个键,说明你已经追平了。 > [!WARNING] > feed 有约 5 秒的在途事务水位:刚写入的行会晚一拍出现。**这不是丢失**,下一轮就会带上它。不要因为「刚改的没立刻出现」就去重置游标做全量重扫。 ## gone 与合并 - `gone: true` 表示这个 id 已经离开公开人口——被合并掉,或不再被服务。删掉本地那一行。 - **被合并的 id 会同时出现在 `/v2/catalog/redirects`**,那里给出接替它的规范 id。有 redirect 就**重指**而不是删除,否则你会丢掉本地所有指向旧 id 的引用。 ```http GET /v2/catalog/redirects?limit=100 { "object": "list", "items": [ { "object": "redirect", "target_object": "work", "old_id": "198221", "current_id": "207379", "merged_at": "2026-08-29T01:12:44Z" } ], "next_cursor": "cur_…" } ``` `redirects` 也是游标 feed,同样可以增量订。`object=` 可以把它限定到某个族。 ## 本地谓词怎么写 如果你把 `content_limit` 缓存成自己的一列用来过滤列表页,请把它写成**可空列**,`NULL` 表示「尚未同步」并且**放行**: ```sql WHERE content_limit IS NULL OR content_limit = 'sfw' ``` 这样冷启动期间未水合的行照常可见,而不是整站空白。真正的权威始终是水合时 catalog 自己的闸,本地这一列只是列表页的快筛。 判定配方(和服务端同一条): ```javascript content_limit = claim.content_limit = content_rating === 'r18' ? 'nsfw' : 'sfw' // 已认领:用认领方的编辑判定 // 未认领:按事实分级 ``` ## 整条回路 ```javascript let cursor = load('catalog_cursor') // 首次为空 = 冷启动 for (;;) { const page = await get('/v2/catalog/changes', { limit: 100, cursor }) const gone = page.items.filter((c) => c.gone).map((c) => c.id) const live = page.items.filter((c) => !c.gone).map((c) => c.id) if (live.length) { const hydrated = await get('/v2/catalog/works', { ids: live.join(','), nsfw: 'true', include: 'covers,titles,companies' }) await upsert(hydrated.items) // missing[] 里的 id 这一轮不可见,按 gone 处理或留待下轮 } if (gone.length) await retireLocal(gone) // 先查 redirects,能重指就重指 if (!page.next_cursor) break // 追平了 cursor = page.next_cursor save('catalog_cursor', cursor) } ``` - [字段裁剪与批量读](/docs/shaping) — `ids=` 的边界、`missing[]` 的语义、include 词表怎么问。 - [限流与配额](/docs/rate-limits) — 冷启动是这个 API 上最大的一次性用量,先算好预算。 --- # 接入用户数据 `/v2/me` 与 `/v2/moderation` 读写的是**某个用户自己的东西**。应用密钥在这两个前缀下一律无效——它证明不了「哪个用户」。 ## 先拿到用户令牌 标准 OAuth 2.0 授权码 + PKCE,端点在 `https://oauth.kungal.com/api/v1`。完整步骤见 [鉴权与凭据](/docs/authentication#user-token)。拿到 `access_token` 后照常放进 `Authorization: Bearer`——和应用密钥同一个位置,但**不要同时带两个**。 > [!WARNING] > 用户面的限流按**用户**计数,不是按 IP。所以一定要带用户令牌调用;用别的方式代理会让你的全体用户挤进同一个桶。 ## 五类用户数据 ### 游玩时长 ```http GET /v2/me/playtimes # 我的全部记录 GET /v2/me/playtimes/{work_id} # 单部作品 PUT /v2/me/playtimes/{work_id} # 覆盖写 POST /v2/me/playtimes # 批量写 DELETE /v2/me/playtimes/{work_id} ``` 这一组**只要用户令牌**,不需要任何额外 scope——任何已开通用户登录的应用都可以调用。批量写用 `POST /v2/me/playtimes`,导入历史记录时别用 `PUT` 逐条打。 ### 认领 ```http GET /v2/me/claims POST /v2/me/claims # 投稿一个认领 GET /v2/me/claims/{id} PATCH /v2/me/claims/{id} # 需要 If-Match DELETE /v2/me/claims/{id} # 只能删草稿 ``` 认领是把目录里的一条记录和你自己站点的条目绑起来。提交时可以带 `refs`(外部锚)或 `field_values`(向导里用户填的字段图)。 > [!NOTE] > 带 `refs` 的提交如果命中了已存在的作品,会返回 `409` 而不是静默新建一条——这是防止你在目录里造出重复条目。先按 `refs=` 查一次,命中就走已有的那条。 ### 编辑提案 ```http GET /v2/me/proposals POST /v2/me/proposals # 提一个修改 GET /v2/me/proposals/{id} PATCH /v2/me/proposals/{id} # 修改或撤回,需要 If-Match POST /v2/me/proposals/{id}/amendments # 追加一条修正,需要 If-Match POST /v2/me/edit-images # 提案里要用的图先传这里 ``` 提案是「我想把这个字段改成那个值」。可改哪些字段由 `GET /v2/catalog/schemas/{object}` 的 `fields` 给出。提案与修订历史是**公开只读**的(`/v2/catalog/proposals`、`/v2/catalog/revisions`)——目录的每一次改动都留痕。 ### 封面投票 ```http GET /v2/me/cover-votes PUT /v2/me/cover-votes/{cover_id} DELETE /v2/me/cover-votes/{cover_id} ``` 封面项的 `id` 在 `GET /v2/catalog/works/{id}/covers` 里——每个能被投票的东西都在读面上有地址,这是硬性设计约束的一个具体例子。 ### 资讯投稿 ```http GET /v2/me/news POST /v2/me/news # 投稿,总是落 pending GET /v2/me/news/{id} PATCH /v2/me/news/{id} # 编辑或撤回 ``` 投稿总是落在 `pending`,由人工过审后才出现在公开的 `/v2/news`。授权模型是「有源行即有资格」:你在资讯源表里有一行,就能替那个源投稿,没有单独的申请流程。 ## 审核面 `/v2/moderation/*` 是给**有审核权的用户**用的,同样是用户令牌,能不能看见由那个用户在对应站点上的权限决定。 ```http GET /v2/moderation/claims # 认领队列 POST /v2/moderation/claims/{id}/decisions # 通过 / 拒绝,需要 If-Match GET /v2/moderation/proposals # 提案队列 POST /v2/moderation/proposals/{id}/decisions # 需要 If-Match POST /v2/moderation/reverts # 回退到某个修订 GET /v2/moderation/snapshots/{object}/{id} # 当前编辑快照 ``` 每个租户只能看见和决定自己站点的东西,跨租户操作是 `403 TENANT_MISMATCH`。 ## 写操作的两个必修课 ### If-Match:不是可选的礼貌 `PATCH /v2/me/claims/{id}`、`PATCH /v2/me/proposals/{id}`、`POST /v2/me/proposals/{id}/amendments` 与两个 `moderation` 决策面都**要求** `If-Match`。流程固定是「GET 拿 `ETag` → 带着它写」: ```javascript const res = await fetch(url, { headers: auth }) const etag = res.headers.get('ETag') const current = await res.json() const write = await fetch(url, { method: 'PATCH', headers: { ...auth, 'If-Match': etag, 'Content-Type': 'application/json' }, body: JSON.stringify(patch) }) // 412 = 有人在你读之后改过:重新 GET,合并,再写一次 ``` ### Idempotency-Key:网络超时不该写两条 所有 `POST` 都支持 `Idempotency-Key`。同一身份、同一路径、同一个 key 的重复请求会重放首次结果(保留 24 小时)。key 请用 UUID 之类的一次性值,并且**同一个 key 只配同一个 body**——body 不同是 `409 IDEMPOTENCY_KEY_REUSED`。 - [拿用户令牌](/docs/authentication) — 授权码 + PKCE 的四步,以及刷新令牌的注意事项。 - [错误处理](/docs/errors) — 412 / 428 / 409 分别意味着什么,该怎么恢复。 --- # 生产最佳实践 一份上线前的清单。每一条后面都有一次真实踩过的坑——不是凭空想出来的谨慎。 ## 凭据 - **应用密钥只放服务端。** 前端要数据就让自己的后端代理。密钥进了浏览器包就等于公开发布。 - **一个产品一个应用。** 出事时能单独吊销,用量也能分开看。 - **轮换不需要停机**:先铸新密钥、灰度切流、再吊销旧的。每个应用可以同时持有 5 把。 - **代表用户读写就用用户令牌**,不要用应用密钥加一个自定义的「user_id」参数——那样的面我们不提供,因为它无法证明是那个用户本人。 ## 客户端契约 1. **忽略未知字段。** 我们只做加法,你的反序列化不能因为多了一个键就抛异常。 2. **容忍开放词表里没见过的取值。** `source`、`platform`、语言这些是开放的,`switch` 必须有 `default`。 3. **为未知的错误 `code` 准备一个按 HTTP `status` 的兜底分支。** 注册表会增长。 > [!WARNING] > 生成客户端时不要把 schema 编译成 `additionalProperties: false` 的严格类型。我们的 spec 明确声明 `additionalProperties: true`,那是加法演进的前提。 ## 把请求数压下去 | 别这么写 | 改成 | | ------------------------------ | ------------------------------------------------- | | 搜索 → 对每条命中打一次详情 | 搜索 → 一次 `ids=` 批量水合(N+1 → 2) | | 手里有 VNDB id,先按标题搜再猜 | `refs=vndb:v19658` 直接反查,最多 100 个一批 | | 每晚全量重扫目录 | 订 [`/v2/catalog/changes`](/docs/mirror) 增量信道 | | `include=` 一次要满 18 个块 | 只要这个页面真正渲染的那几块 | | 给列表页每一项单独请求封面 | `include=covers` 随列表一起批量水合 | ## 错误与重试 - **`4xx` 不重试**(`429` 除外)。它是你的请求有 bug,重试一百次也一样。 - **`429` 按 `Retry-After` 等**,并且加抖动。整点齐发的定时任务会一起撞墙、再一起重试。 - **`QUOTA_EXCEEDED` 不要退避重试**——今天没额度了,等待没有意义。 - **`5xx` 指数退避 + 抖动**,并设上限;连续失败就降级到本地缓存,而不是把重试队列堆到明天。 - **日志里记 `X-Request-ID`。** 报问题时带上它,我们能直接定位到那一次请求。 ## 缓存与新鲜度 - 存下 `ETag`,回访时带 `If-None-Match`——省带宽也省解析。 - 不要把 `/v2/catalog/*` 的响应交给不理解围栏的共享中间层:它的 body 会随凭据能力位变化,声明就是 `private, no-store`。 - 需要在自己 SQL 里过滤就做 [镜像](/docs/mirror),别在请求路径上硬扛。 - 镜像的本地判定列写成**可空且 NULL 放行**,冷启动期间不会整站空白。 ## NSFW 与分级 - `nsfw=` 是你自己控制的闸,**默认隐藏 r18**。要全量就显式传 `nsfw=true`,尤其是在做镜像的时候——不传会在本地留下你察觉不到的空洞。 - `content_rating`(事实分级)和 `content_limit`(编辑展示轴)是两个不同的东西,不要混用。 - 别把 `nsfw=true` 硬编码进面向终端用户的读路径——那应该跟着用户自己的设置走。 ## 监控 - 给 `Deprecation` / `Sunset` 响应头设告警。它们出现的那天你还有从容迁移的时间。 - 把 `RateLimit-Remaining` 打进指标,在撞墙之前就能看见趋势。 - 定期 diff `/v2/catalog/openapi.json`(免密钥)——加了什么字段、开了什么新端点,一眼就知道。 - `4xx` 按 `code` 分组统计。突然冒出来的 `UNKNOWN_INCLUDE` 通常意味着有人拼错了参数名。 ## 署名 用了这份数据,请按门户首页写明的方式标注来源。这不是法务条款,是对六个上游和所有做整理的编辑的基本尊重。 - [增量镜像目录](/docs/mirror) — 需要在自己库里过滤时的完整配方。 - [AI / MCP 接入](/docs/mcp) — 让 AI 助手直接调用,不用自己写 HTTP 客户端。 --- # Problem types ## 错误码注册表(顶层 `code`,共 41 个) `code` 是封闭注册表里的稳定标识;`errors[].reason` 是另一套互不重叠的字段级词表。认不得的 `code` 一律按 `status` 兜底——我们会往注册表里加新成员。 ### platform | code | HTTP | title | description | | --- | --- | --- | --- | | `MALFORMED_BODY` | 400 | Malformed body | Request body is not valid JSON, or is not a valid instance of the declared media type. | | `INVALID_PARAMETER` | 400 | Invalid parameter | A parameter is syntactically wrong: a boolean that is not true/false, an integer that is not an integer, a date that is not YYYY-MM-DD. | | `UNKNOWN_ENUM_VALUE` | 400 | Unknown enum value | A closed vocabulary received an unknown token at parse time. | | `MUTUALLY_EXCLUSIVE_PARAMETERS` | 400 | Mutually exclusive parameters | Two parameters that cannot appear together were both sent. | | `LIMIT_TOO_LARGE` | 400 | Limit too large | limit is greater than 100. The value is not clamped. | | `TOO_MANY_IDS` | 400 | Too many ids | ids= or refs= named more than 100 items. | | `INVALID_CURSOR` | 400 | Invalid cursor | The cursor cannot be parsed or is no longer valid. | | `UNKNOWN_INCLUDE` | 400 | Unknown include | include= received a token that is not in that collection's vocabulary. | | `UNKNOWN_FIELD` | 400 | Unknown field | fields= received a top-level key that does not exist. | | `UNKNOWN_SORT` | 400 | Unknown sort | sort= received a key this collection has not declared. | | `UNKNOWN_FACET` | 400 | Unknown facet | facets= received a name this collection does not support. | | `MISSING_CREDENTIAL` | 401 | Missing credential | The request has no Authorization header. | | `INVALID_CREDENTIAL` | 401 | Invalid credential | A credential was sent but it is invalid, expired, or revoked. | | `SCOPE_REQUIRED` | 403 | Scope required | The credential is valid but lacks the scope this operation needs. | | `FIRST_PARTY_ONLY` | 403 | First party only | This operation is only available to first-party clients. | | `NOT_FOUND` | 404 | Not found | Nothing visible exists at this URL. | | `METHOD_NOT_ALLOWED` | 405 | Method not allowed | The path exists but this method does not. | | `IDEMPOTENCY_KEY_REUSED` | 409 | Idempotency key reused | The same Idempotency-Key was sent with a different request body. | | `GONE` | 410 | Gone | This URL existed and has been permanently retired. | | `PRECONDITION_FAILED` | 412 | Precondition failed | If-Match did not match the current representation. | | `UNSUPPORTED_MEDIA_TYPE` | 415 | Unsupported media type | The request body media type is not supported. | | `VALIDATION_FAILED` | 422 | Validation failed | The request is syntactically valid but semantically not. errors[] is present and non-empty. | | `PRECONDITION_REQUIRED` | 428 | Precondition required | This operation requires If-Match and none was sent. | | `RATE_LIMITED` | 429 | Rate limited | The short-window rate limit was exceeded. Retry-After is in seconds. | | `QUOTA_EXCEEDED` | 429 | Quota exceeded | The daily quota is exhausted. Retry-After is until the next window. | | `INTERNAL_ERROR` | 500 | Internal error | A bug on our side, including the output of panic recovery. | | `SERVICE_UNAVAILABLE` | 503 | Service unavailable | A dependency is unavailable. The request may be retried. | ### catalog | code | HTTP | title | description | | --- | --- | --- | --- | | `ENTITY_MERGED` | 404 | Entity merged | The entity was merged into another. object and current_id are present, and Link rel=canonical is sent. | ### me | code | HTTP | title | description | | --- | --- | --- | --- | | `USER_IDENTITY_REQUIRED` | 403 | User identity required | An application key was used for an operation that needs a user token. | | `SITE_NOT_BOUND` | 403 | Site not bound | The client behind the token is not bound to a catalog site. | | `RELEASE_CREATION_DISABLED` | 403 | Release creation disabled | The proposal tried to create a release. This is a product constraint, not a defect. | | `ALREADY_EXISTS` | 409 | Already exists | The same subject already has a live record for this target. | | `INVALID_STATE_TRANSITION` | 409 | Invalid state transition | The current state does not allow this transition. detail names the current state and the legal targets. | | `CLAIM_NOT_OWNED` | 403 | Claim not owned | The claim has an owner and it is another user. Only the owner may publish, submit, or withdraw it; an unowned claim is adopted by its first claimant. | ### moderation | code | HTTP | title | description | | --- | --- | --- | --- | | `PERMISSION_REQUIRED` | 403 | Permission required | The token lacks the permission this decision needs. | | `TENANT_MISMATCH` | 403 | Tenant mismatch | The target does not belong to the caller's catalog site. | | `DECISION_ALREADY_MADE` | 409 | Decision already made | This item has already been decided. detail names who decided and when. | ### news | code | HTTP | title | description | | --- | --- | --- | --- | | `SOURCE_NOT_YOURS` | 403 | Source not yours | The named news source is not bound to this user. A source that does not exist is not distinguished, so source names cannot be enumerated. | | `SOURCE_INACTIVE` | 422 | Source inactive | The news source is bound correctly but has been deactivated. detail names who to ask to restore it. | ### store | code | HTTP | title | description | | --- | --- | --- | --- | | `STORE_QUOTA_EXCEEDED` | 403 | Store quota exceeded | The application has minted the maximum number of purchase links. | | `STORE_LINK_UNAVAILABLE` | 502 | Store link unavailable | The link shortener is unavailable; no link was issued — there is deliberately no fallback to a bare affiliate URL. | --- # Vocabularies ## 词表(共 22 个) 封闭词表(`closed`)不加不减成员就是它的承诺——你的 `switch` 可以没有 `default`。开放词表相反:提前告诉你会有新值,客户端必须容忍没见过的取值。运行时以 `https://api.nextmoe.dev/v2/vocabularies` 为准,它比任何文档都新,且不需要凭据。 ### `medium` Closed · 7 tokens | value | display_name | description | | --- | --- | --- | | `galgame` | Galgame | A galgame / visual novel work. | | `manga` | Manga | A manga work. | | `novel` | Novel | A light novel or other prose work. | | `anime` | Anime | An anime work. | | `asmr` | ASMR | An ASMR work. | | `doujin_game` | Doujin game | A doujin game that is not classified as galgame. | | `music` | Music | A music work. | ### `content_rating` Closed · 3 tokens | value | display_name | description | | --- | --- | --- | | `all_ages` | All ages | Rated suitable for all ages. | | `sensitive` | Sensitive | Sexual or violent content below the r18 line. | | `r18` | R18 | Adult-only content. | ### `sexual` Closed · 3 tokens | value | display_name | description | | --- | --- | --- | | `safe` | Safe | No sexual depiction. | | `suggestive` | Suggestive | Suggestive sexual depiction. | | `explicit` | Explicit | Explicit sexual depiction. | ### `violence` Closed · 3 tokens | value | display_name | description | | --- | --- | --- | | `tame` | Tame | No violent depiction. | | `violent` | Violent | Violent depiction short of brutality. | | `brutal` | Brutal | Brutal violent depiction. | ### `spoiler` Closed · 3 tokens | value | display_name | description | | --- | --- | --- | | `none` | None | Not a spoiler. | | `minor` | Minor | Minor spoiler. | | `major` | Major | Major spoiler. | ### `gender` Closed · 3 tokens | value | display_name | description | | --- | --- | --- | | `male` | Male | Male. | | `female` | Female | Female. | | `other` | Other | Recorded as other. Absence of a record is null, not a value. | ### `blood_type` Closed · 4 tokens | value | display_name | description | | --- | --- | --- | | `a` | A | Blood type A. | | `b` | B | Blood type B. | | `ab` | AB | Blood type AB. | | `o` | O | Blood type O. | ### `release_status` Closed · 5 tokens | value | display_name | description | | --- | --- | --- | | `released` | Released | The release has shipped. | | `dated` | Dated | A date is known and the release has not shipped. | | `announced` | Announced | Announced without a usable date. | | `cancelled` | Cancelled | Cancelled. | | `unknown` | Unknown | We have not determined the status. | ### `claim_state` Closed · 5 tokens | value | display_name | description | | --- | --- | --- | | `live` | Live | The claim is live. | | `draft` | Draft | The claim is a draft. | | `pending` | Pending | The claim is waiting for review. | | `declined` | Declined | The claim was declined. | | `hidden` | Hidden | The claim is hidden. | ### `content_limit` Closed · 2 tokens | value | display_name | description | | --- | --- | --- | | `sfw` | SFW | Safe to render in an SFW context. | | `nsfw` | NSFW | Not safe to render in an SFW context. | ### `tier` Closed · 3 tokens | value | display_name | description | | --- | --- | --- | | `core` | Core | Core vocabulary tag. | | `longtail` | Longtail | Long-tail tag. | | `hidden` | Hidden | Hidden from default listings. | ### `title_kind` Closed · 3 tokens | value | display_name | description | | --- | --- | --- | | `official` | Official | Official title. | | `alias` | Alias | Alternate title. | | `abbreviation` | Abbreviation | Abbreviated title. search_hint is internal and is not in this vocabulary. | ### `alias_kind` Closed · 2 tokens | value | display_name | description | | --- | --- | --- | | `translation` | Translation | A translated name. | | `spelling_variant` | Spelling variant | A spelling variant. search_hint is internal and is not in this vocabulary. | ### `roster_role` Closed · 4 tokens | value | display_name | description | | --- | --- | --- | | `unknown` | Unknown | The roster role was not recorded. | | `main` | Main | A main character. | | `secondary` | Secondary | A secondary character. | | `appears` | Appears | A character who appears. | ### `attribution_role` Closed · 4 tokens | value | display_name | description | | --- | --- | --- | | `circle` | Circle | Circle / doujin group attribution. | | `publisher` | Publisher | Publisher attribution. | | `developer` | Developer | Developer attribution. | | `brand` | Brand | Brand attribution. | ### `company_kind` Closed · 6 tokens | value | display_name | description | | --- | --- | --- | | `game_brand` | Game brand | A game brand. | | `bunko` | Bunko | A bunko / imprint. | | `publisher` | Publisher | A publisher. | | `anime_studio` | Anime studio | An anime studio. | | `doujin_circle` | Doujin circle | A doujin circle. | | `group` | Group | A group. | ### `tag_kind` Closed · 2 tokens | value | display_name | description | | --- | --- | --- | | `content` | Content | A content tag. | | `meta` | Meta | A meta tag. | ### `release_kind` Closed · 5 tokens | value | display_name | description | | --- | --- | --- | | `default` | Default | The default / unspecified edition. | | `digital` | Digital | A digital edition. | | `physical` | Physical | A physical edition. | | `trial` | Trial | A trial / demo edition. | | `patch` | Patch | A patch, including fan translations. | ### `member_kind` Closed · 5 tokens | value | display_name | description | | --- | --- | --- | | `unknown` | Unknown | The series-member role was not recorded. | | `main` | Main | The main entry of the series. | | `fandisc` | Fandisc | A fandisc. | | `side_story` | Side story | A side story. | | `collection` | Collection | A collection / omnibus. | ### `problem_domain` Closed · 6 tokens | value | display_name | description | | --- | --- | --- | | `platform` | Platform | Errors any face may emit. | | `catalog` | Catalog | Catalog-face errors. | | `me` | Me | User-facing /v2/me errors. | | `moderation` | Moderation | Moderation-face errors. | | `news` | News | News-face errors. Both codes come from the source-row-as-grant model on /v2/me/news. | | `store` | Store | Store-face errors from the purchase-link minter. | ### `sources` Open · 20 tokens | value | display_name | description | | --- | --- | --- | | `user` | User | Manual curation, not an import source. | | `vndb` | VNDB | The VNDB catalog. | | `bangumi` | Bangumi | The Bangumi catalog. | | `dlsite` | DLsite | DLsite storefront identities. | | `erogamescape` | ErogameScape | ErogameScape. | | `anilist` | AniList | AniList. | | `mal` | MyAnimeList | MyAnimeList. | | `steam` | Steam | Steam storefront. | | `official_site` | Official site | A publisher or brand official site. | | `twitter` | Twitter / X | A Twitter / X identity. | | `pixiv` | Pixiv | Pixiv. | | `curated` | Curated | First-party curated / human lane. | | `upscale` | Upscale | First-party AI-upscaled cover derivation. | | `cien` | Ci-en | Ci-en creator-support platform. | | `dmm` | DMM | DMM storefront. | | `web` | Web | A generic web page. external_id is the full URL. | | `getchu` | Getchu | Getchu.com retailer pages. | | `derived` | Derived | First-party machine inference over catalog facts. | | `nextmoe` | NextMoe | First-party measurements aggregated from our users. | | `howlongtobeat` | HowLongToBeat | HowLongToBeat playtime and rating aggregates. | ### `relation_types` Open · 16 tokens | value | display_name | description | | --- | --- | --- | | `adaptation_of` | Adaptation of | This work is an adaptation of the other. | | `sequel_of` | Sequel of | This work is a sequel of the other. | | `side_story_of` | Side story of | This work is a side story of the other. | | `fandisc_of` | Fandisc of | This work is a fandisc of the other. | | `collects` | Collects | This work collects the other. | | `remake_of` | Remake of | This work is a remake of the other. | | `same_series` | Same series | The two works belong to the same series. Symmetric. | | `same_setting` | Same setting | The two works share a setting. Symmetric. | | `crossover_with` | Crossover with | The two works crossover. Symmetric. | | `shares_character` | Shares character | The two works share a character. Symmetric. | | `alternative_setting` | Alternative setting | The two works share characters in a different setting. Symmetric. | | `alternative_version` | Alternative version | The two works are alternative versions. Symmetric. | | `imprint_of` | Imprint of | This company is an imprint of the other. | | `renamed_from` | Renamed from | This company was renamed from the other. | | `subsidiary_of` | Subsidiary of | This company is a subsidiary of the other. | | `member_of` | Member of | This company is a member of the other. | --- # Public API v2 - 路径前缀:`/v2` - 凭据:Authorization: Bearer nmk_live_…(v2 应用密钥,门户自助铸造,无需申请) - 端点数:90 > 正式公开:形状按 additive-only 演进,删除与改名由 CI 的 oasdiff 门拦下。第三方在门户自助铸 nmk_ 密钥即可调用,不需要申请。 > /v2/me/playtimes 只要用户令牌,不需要 playtime:read / playtime:write。任何已开通用户登录的应用都可以调用。 > 错误体是 RFC 9457 application/problem+json。type URI 解析到本站 /problems/{domain}/{kebab-code}。 > 客户端必须忽略未知字段、容忍开放词表中未见过的取值,并为未知错误 code 准备一个按 HTTP status 的兜底分支。 ## 注册表 ### GET /v2/problems List every top-level error code The closed registry of top-level error codes. Keyset-paginated. Unauthenticated. - 所属 API:Public API v2(/v2) - 鉴权:无需凭据 - scope:无需凭据 | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `cursor` | query | 否 | string | Opaque keyset cursor from a prior next_cursor. Must start with cur_. | | `limit` | query | 否 | string | Page size 1-100, default 20. Values above 100 are 400 LIMIT_TOO_LARGE, not clamped. | | `view` | query | 否 | string | basic (default) or full. Closed vocabulary. | | `include` | query | 否 | string | Comma-separated blocks. Unknown token is 400 UNKNOWN_INCLUDE. | | `fields` | query | 否 | string | Comma-separated top-level keys after view/include. Unknown token is 400 UNKNOWN_FIELD. object and id are always kept. | | `ids` | query | 否 | string | Comma-separated ids, max 100. Batch lane: no pagination. | | `refs` | query | 否 | string | Comma-separated source:external_id, max 100. Batch lane: no pagination. | | `include_total` | query | 否 | string | true to include total. Only true or false. | | `facets` | query | 否 | string | Comma-separated facet names. Unknown token is 400 UNKNOWN_FACET. | | `sort` | query | 否 | string | Closed per-collection sort key. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | ```bash curl "https://api.nextmoe.dev/v2/problems" ``` ### GET /v2/problems/reasons List every field-level error reason The closed registry of field-level reasons. Unauthenticated. Values in this list never appear as top-level codes. - 所属 API:Public API v2(/v2) - 鉴权:无需凭据 - scope:无需凭据 无参数。 ```bash curl "https://api.nextmoe.dev/v2/problems/reasons" ``` ### GET /v2/problems/{code} Get one top-level error code Returns the registry entry for one code. Unknown codes are 404 NOT_FOUND, not 422 — the path parameter is a lookup key, not a closed enum. - 所属 API:Public API v2(/v2) - 鉴权:无需凭据 - scope:无需凭据 | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `code` | path | 是 | string | Top-level error code from the registry (UPPER_SNAKE). | ```bash curl "https://api.nextmoe.dev/v2/problems/value" ``` ### GET /v2/vocabularies List published vocabularies Closed and seed-open vocabularies. Keyset-paginated. Unauthenticated. - 所属 API:Public API v2(/v2) - 鉴权:无需凭据 - scope:无需凭据 | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `cursor` | query | 否 | string | Opaque keyset cursor from a prior next_cursor. Must start with cur_. | | `limit` | query | 否 | string | Page size 1-100, default 20. Values above 100 are 400 LIMIT_TOO_LARGE, not clamped. | | `view` | query | 否 | string | basic (default) or full. Closed vocabulary. | | `include` | query | 否 | string | Comma-separated blocks. Unknown token is 400 UNKNOWN_INCLUDE. | | `fields` | query | 否 | string | Comma-separated top-level keys after view/include. Unknown token is 400 UNKNOWN_FIELD. object and id are always kept. | | `ids` | query | 否 | string | Comma-separated ids, max 100. Batch lane: no pagination. | | `refs` | query | 否 | string | Comma-separated source:external_id, max 100. Batch lane: no pagination. | | `include_total` | query | 否 | string | true to include total. Only true or false. | | `facets` | query | 否 | string | Comma-separated facet names. Unknown token is 400 UNKNOWN_FACET. | | `sort` | query | 否 | string | Closed per-collection sort key. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | ```bash curl "https://api.nextmoe.dev/v2/vocabularies" ``` ### GET /v2/vocabularies/{name} Get one vocabulary Returns every published value of one vocabulary. Unknown names are 404 NOT_FOUND. - 所属 API:Public API v2(/v2) - 鉴权:无需凭据 - scope:无需凭据 | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `name` | path | 是 | string | Vocabulary name. The path segment of /v2/vocabularies/{name}. | ```bash curl "https://api.nextmoe.dev/v2/vocabularies/value" ``` ## 目录 ### GET /v2/catalog/calendar Release calendar One collection. month=/year= pick a window; precision= and status= select among the dated month, year-only, and undated views that were three v1 routes. content_limit= gates on the editorial display axis and olang= on the original language (absent = ja plus zh). meta carries today plus, on the dated month window, min_month/max_month/has_prev/has_next for month navigation. Requires an application key. ids= is not accepted. include=titles,refs,intros,covers,companies,ratings,tags,credits fills on this lane; view=full is all of them except credits, which is an explicit ask. On a collection lane titles elects latin/localized and covers elects the two cover slots that grade the base cover — the full titles[] and covers[] arrays, and relations/releases/popularity/playtimes/series/platforms/screenshots/characters/engines/links, are per-record blocks and live on /v2/catalog/works/{id} and its sub-resources; asking for one here is 400 UNKNOWN_INCLUDE. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `cursor` | query | 否 | string | Opaque keyset cursor from a prior next_cursor. Must start with cur_. | | `limit` | query | 否 | string | Page size 1-100, default 20. Values above 100 are 400 LIMIT_TOO_LARGE, not clamped. | | `view` | query | 否 | string | basic (default) or full. Closed vocabulary. | | `include` | query | 否 | string | Comma-separated blocks. Unknown token is 400 UNKNOWN_INCLUDE. | | `fields` | query | 否 | string | Comma-separated top-level keys after view/include. Unknown token is 400 UNKNOWN_FIELD. object and id are always kept. | | `ids` | query | 否 | string | Comma-separated ids, max 100. Batch lane: no pagination. | | `refs` | query | 否 | string | Comma-separated source:external_id, max 100. Batch lane: no pagination. | | `include_total` | query | 否 | string | true to include total. Only true or false. | | `facets` | query | 否 | string | Comma-separated facet names. Unknown token is 400 UNKNOWN_FACET. | | `sort` | query | 否 | string | Closed per-collection sort key. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | | `month` | query | 否 | string | Dated month window YYYY-MM. Default: current month in Asia/Tokyo. | | `year` | query | 否 | string | Year-only window YYYY (v1 pending). Default with precision=year: current year in Asia/Tokyo. | | `precision` | query | 否 | string | day, month, or year. year selects the year-only window. day and month use the dated month window. | | `status` | query | 否 | string | released, dated, announced, cancelled, unknown. announced and unknown select the undated window. cancelled is empty until the catalog records cancellations. | | `content_limit` | query | 否 | string | Comma-separated closed editorial axis: sfw, nsfw. | | `olang` | query | 否 | string | Comma-separated BCP-47, or all. Open vocabulary; unknown values match nothing. Absent = the calendar's home population, ja plus zh. | ```bash curl "https://api.nextmoe.dev/v2/catalog/calendar" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/changes Catalog changes feed Works updated recently, oldest first. Keyset-paginated. Requires an application key. ids= is not accepted. **This is the mirror channel.** If you cache any catalog-owned fact per work — above all the editorial display axis `content_limit`, whose verdict is `claimed_by.content_limit` when the claim block is present and otherwise `nsfw` when `content_rating` is `r18`, `sfw` otherwise — poll this feed instead of sweeping the catalog. Every write that changes a work's claim state, its display axis (the editorial NSFW flag or its content rating), or its existence bumps `updated_at` and surfaces the id here. Bootstrap from an empty cursor: the feed enumerates the whole population oldest-updated-first, so the first drain IS the full inventory. Hydrate each page against /v2/catalog/works with ids= in batches of at most 100, with both gates open (nsfw=true and no content_limit), then keep the cursor and poll it at your own cadence. gone: an entry carrying `gone: true` has left the public population — drop the mirrored row. Merged-away ids appear here as gone AND in /v2/catalog/redirects, which names the id that replaced them; repoint rather than delete when the redirect exists. Everything else a work serves — covers, tags, titles, intros, ratings — surfaces best-effort: most of those writers touch the work too, but only claim state, the display axis and existence are promised. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `cursor` | query | 否 | string | Opaque keyset cursor from a prior next_cursor. Must start with cur_. | | `limit` | query | 否 | string | Page size 1-100, default 20. Values above 100 are 400 LIMIT_TOO_LARGE, not clamped. | | `view` | query | 否 | string | basic (default) or full. Closed vocabulary. | | `include` | query | 否 | string | Comma-separated blocks. Unknown token is 400 UNKNOWN_INCLUDE. | | `fields` | query | 否 | string | Comma-separated top-level keys after view/include. Unknown token is 400 UNKNOWN_FIELD. object and id are always kept. | | `ids` | query | 否 | string | Comma-separated ids, max 100. Batch lane: no pagination. | | `refs` | query | 否 | string | Comma-separated source:external_id, max 100. Batch lane: no pagination. | | `include_total` | query | 否 | string | true to include total. Only true or false. | | `facets` | query | 否 | string | Comma-separated facet names. Unknown token is 400 UNKNOWN_FACET. | | `sort` | query | 否 | string | Closed per-collection sort key. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | ```bash curl "https://api.nextmoe.dev/v2/catalog/changes" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/characters List characters Keyset-paginated characters. Requires an application key. ids=/refs= is a batch lane and does not paginate. include=gender,birthday,height_cm,weight_kg,measurements,blood_type,instance_of_id,image,figure,traits,aliases,intros,refs fills on every lane, and view=full is all of them; traits are cut at the default spoiler ceiling and follow the nsfw gate, exactly as on the detail face. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `cursor` | query | 否 | string | Opaque keyset cursor from a prior next_cursor. Must start with cur_. | | `limit` | query | 否 | string | Page size 1-100, default 20. Values above 100 are 400 LIMIT_TOO_LARGE, not clamped. | | `view` | query | 否 | string | basic (default) or full. Closed vocabulary. | | `include` | query | 否 | string | Comma-separated blocks. Unknown token is 400 UNKNOWN_INCLUDE. | | `fields` | query | 否 | string | Comma-separated top-level keys after view/include. Unknown token is 400 UNKNOWN_FIELD. object and id are always kept. | | `ids` | query | 否 | string | Comma-separated ids, max 100. Batch lane: no pagination. | | `refs` | query | 否 | string | Comma-separated source:external_id, max 100. Batch lane: no pagination. | | `include_total` | query | 否 | string | true to include total. Only true or false. | | `facets` | query | 否 | string | Comma-separated facet names. Unknown token is 400 UNKNOWN_FACET. | | `sort` | query | 否 | string | Closed per-collection sort key. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | ```bash curl "https://api.nextmoe.dev/v2/catalog/characters" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/characters/{id} Get one character Character detail. view=full adds gender, birthday, measurements, blood_type, instance_of_id. include=image,figure,traits,aliases,intros,refs adds art, trait, name, description and anchor blocks. spoiler=none|minor|major is the ceiling of the traits block and defaults to none. Merged ids are 404 ENTITY_MERGED. Requires an application key. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | Decimal catalog id. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | | `view` | query | 否 | string | basic (default) or full. Closed vocabulary. | | `include` | query | 否 | string | Comma-separated blocks. Unknown token is 400 UNKNOWN_INCLUDE. | | `fields` | query | 否 | string | Comma-separated top-level keys. Unknown token is 400 UNKNOWN_FIELD. object and id are always kept. | | `spoiler` | query | 否 | string | Spoiler ceiling for the traits block: none (default), minor or major. Closed vocabulary; an unknown value is 400. Trait rows above the ceiling are not returned. Only the VNDB-derived trait vocabulary carries a spoiler level, and the default is the safe ceiling. | ```bash curl "https://api.nextmoe.dev/v2/catalog/characters/value" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/characters/{id}/appearances Appearances of one character Works this character appears in, with roster_role, spoiler, and voice credits. Offset cursor. Requires an application key. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | Decimal catalog work id. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | | `cursor` | query | 否 | string | Opaque keyset cursor from a prior next_cursor. Must start with cur_. | | `limit` | query | 否 | string | Page size 1-100, default 20. | ```bash curl "https://api.nextmoe.dev/v2/catalog/characters/value/appearances" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/claim-events Claim event history Every claim lifecycle transition, newest first by default. sort=recorded_asc walks the same collection oldest-first by id, which is the shape a mirror or a reward cron reads with a watermark. Requires an application key with the claim_events:read scope on top of catalog:read; the scope is granted by an operator, not self-service, because events carry decline reasons and moderator actions. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read + claim_events:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `cursor` | query | 否 | string | Opaque keyset cursor from a prior next_cursor. Must start with cur_. | | `limit` | query | 否 | string | Page size 1-100, default 20. Values above 100 are 400 LIMIT_TOO_LARGE, not clamped. | | `view` | query | 否 | string | basic (default) or full. Closed vocabulary. | | `fields` | query | 否 | string | Comma-separated top-level keys. Unknown token is 400 UNKNOWN_FIELD. object and id are always kept. | | `ids` | query | 否 | string | Comma-separated event ids, max 100. Batch lane: no pagination. | | `include_total` | query | 否 | string | true to include total. Only true or false. | | `sort` | query | 否 | string | recorded_desc (default) or recorded_asc. Closed. recorded_asc is the watermark walk a mirror reads. | | `site` | query | 否 | string | Tenant key the event was recorded under. Open vocabulary; unknown values match nothing. | | `actor_uid` | query | 否 | string | The claiming site's own user id of the actor. Not a catalog id. | | `work_id` | query | 否 | string | Catalog work id to narrow to one work's claim history. | ```bash curl "https://api.nextmoe.dev/v2/catalog/claim-events" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/companies List companies Keyset-paginated company registry (v1 labels). Requires an application key. ids=/refs= is a batch lane and does not paginate. has_works=true keeps only companies with works visible under the same nsfw gate. include=aliases,logo fills on every lane; include=intros,links fills on the batch lane only (and on the detail face). - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `cursor` | query | 否 | string | Opaque keyset cursor from a prior next_cursor. Must start with cur_. | | `limit` | query | 否 | string | Page size 1-100, default 20. Values above 100 are 400 LIMIT_TOO_LARGE, not clamped. | | `view` | query | 否 | string | basic (default) or full. Closed vocabulary. | | `include` | query | 否 | string | Comma-separated blocks. Unknown token is 400 UNKNOWN_INCLUDE. | | `fields` | query | 否 | string | Comma-separated top-level keys after view/include. Unknown token is 400 UNKNOWN_FIELD. object and id are always kept. | | `ids` | query | 否 | string | Comma-separated ids, max 100. Batch lane: no pagination. | | `refs` | query | 否 | string | Comma-separated source:external_id, max 100. Batch lane: no pagination. | | `include_total` | query | 否 | string | true to include total. Only true or false. | | `facets` | query | 否 | string | Comma-separated facet names. Unknown token is 400 UNKNOWN_FACET. | | `sort` | query | 否 | string | Closed per-collection sort key. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | | `has_works` | query | 否 | string | true keeps only companies whose work_count is > 0 under the same nsfw gate. Only true or false. Absent = every company. | ```bash curl "https://api.nextmoe.dev/v2/catalog/companies" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/companies/{id} Get one company Company registry row (v1 labels). include=aliases,logo,intros,links adds the corresponding blocks. Merged ids are 404 ENTITY_MERGED. Requires an application key. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | Decimal catalog id. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | | `view` | query | 否 | string | basic (default) or full. Closed vocabulary. | | `include` | query | 否 | string | Comma-separated blocks. Unknown token is 400 UNKNOWN_INCLUDE. | | `fields` | query | 否 | string | Comma-separated top-level keys. Unknown token is 400 UNKNOWN_FIELD. object and id are always kept. | ```bash curl "https://api.nextmoe.dev/v2/catalog/companies/value" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/companies/{id}/graph Company family graph Corporate-family nodes and directed edges around one company. Inverse relations are not emitted. include= is validated against the company token set, so aliases, intros and links are accepted and answered without; only include=logo changes a node, adding the brand mark to the nodes that have one. Merged ids are 404 ENTITY_MERGED. Requires an application key. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | Decimal catalog id. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | | `view` | query | 否 | string | basic (default) or full. Closed vocabulary. | | `include` | query | 否 | string | Comma-separated blocks. Unknown token is 400 UNKNOWN_INCLUDE. | | `fields` | query | 否 | string | Comma-separated top-level keys. Unknown token is 400 UNKNOWN_FIELD. object and id are always kept. | ```bash curl "https://api.nextmoe.dev/v2/catalog/companies/value/graph" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/credit-names List credit names Keyset-paginated credited names. q= filters by name. Requires an application key. ids=/refs= is a batch lane and does not paginate. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `cursor` | query | 否 | string | Opaque keyset cursor from a prior next_cursor. Must start with cur_. | | `limit` | query | 否 | string | Page size 1-100, default 20. Values above 100 are 400 LIMIT_TOO_LARGE, not clamped. | | `view` | query | 否 | string | basic (default) or full. Closed vocabulary. | | `include` | query | 否 | string | Comma-separated blocks. Unknown token is 400 UNKNOWN_INCLUDE. | | `fields` | query | 否 | string | Comma-separated top-level keys after view/include. Unknown token is 400 UNKNOWN_FIELD. object and id are always kept. | | `ids` | query | 否 | string | Comma-separated ids, max 100. Batch lane: no pagination. | | `refs` | query | 否 | string | Comma-separated source:external_id, max 100. Batch lane: no pagination. | | `include_total` | query | 否 | string | true to include total. Only true or false. | | `facets` | query | 否 | string | Comma-separated facet names. Unknown token is 400 UNKNOWN_FACET. | | `sort` | query | 否 | string | Closed per-collection sort key. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | | `q` | query | 否 | string | Name search. Empty lists by id. Must not be used as a discriminant. | ```bash curl "https://api.nextmoe.dev/v2/catalog/credit-names" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/credit-names/{id} Get one credit name A credited name, not a person — this is the staff and voice-actor read surface. person_id is null when unlinked; gender and the fuzzy birth parts are person-level facts reached through that link. include=aliases,photo,siblings,intros,links,refs adds the corresponding blocks. Works this name is credited on live at /v2/catalog/credit-names/{id}/credits. Requires an application key. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | Decimal catalog id. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | | `view` | query | 否 | string | basic (default) or full. Closed vocabulary. | | `include` | query | 否 | string | Comma-separated blocks. Unknown token is 400 UNKNOWN_INCLUDE. | | `fields` | query | 否 | string | Comma-separated top-level keys. Unknown token is 400 UNKNOWN_FIELD. object and id are always kept. | ```bash curl "https://api.nextmoe.dev/v2/catalog/credit-names/value" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/credit-names/{id}/credits Credits of one credit name Works this name is credited on. Offset cursor. Requires an application key. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | Decimal catalog work id. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | | `cursor` | query | 否 | string | Opaque keyset cursor from a prior next_cursor. Must start with cur_. | | `limit` | query | 否 | string | Page size 1-100, default 20. | ```bash curl "https://api.nextmoe.dev/v2/catalog/credit-names/value/credits" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/engines List engines Keyset-paginated engines. Requires an application key. ids=/refs= is a batch lane and does not paginate. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `cursor` | query | 否 | string | Opaque keyset cursor from a prior next_cursor. Must start with cur_. | | `limit` | query | 否 | string | Page size 1-100, default 20. Values above 100 are 400 LIMIT_TOO_LARGE, not clamped. | | `view` | query | 否 | string | basic (default) or full. Closed vocabulary. | | `include` | query | 否 | string | Comma-separated blocks. Unknown token is 400 UNKNOWN_INCLUDE. | | `fields` | query | 否 | string | Comma-separated top-level keys after view/include. Unknown token is 400 UNKNOWN_FIELD. object and id are always kept. | | `ids` | query | 否 | string | Comma-separated ids, max 100. Batch lane: no pagination. | | `refs` | query | 否 | string | Comma-separated source:external_id, max 100. Batch lane: no pagination. | | `include_total` | query | 否 | string | true to include total. Only true or false. | | `facets` | query | 否 | string | Comma-separated facet names. Unknown token is 400 UNKNOWN_FACET. | | `sort` | query | 否 | string | Closed per-collection sort key. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | ```bash curl "https://api.nextmoe.dev/v2/catalog/engines" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/engines/{id} Get one engine Engine detail. Requires an application key. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | Decimal catalog id. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | | `view` | query | 否 | string | basic (default) or full. Closed vocabulary. | | `include` | query | 否 | string | Comma-separated blocks. Unknown token is 400 UNKNOWN_INCLUDE. | | `fields` | query | 否 | string | Comma-separated top-level keys. Unknown token is 400 UNKNOWN_FIELD. object and id are always kept. | ```bash curl "https://api.nextmoe.dev/v2/catalog/engines/value" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/persons List persons Keyset-paginated persons. Requires an application key. ids=/refs= is a batch lane and does not paginate. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `cursor` | query | 否 | string | Opaque keyset cursor from a prior next_cursor. Must start with cur_. | | `limit` | query | 否 | string | Page size 1-100, default 20. Values above 100 are 400 LIMIT_TOO_LARGE, not clamped. | | `view` | query | 否 | string | basic (default) or full. Closed vocabulary. | | `include` | query | 否 | string | Comma-separated blocks. Unknown token is 400 UNKNOWN_INCLUDE. | | `fields` | query | 否 | string | Comma-separated top-level keys after view/include. Unknown token is 400 UNKNOWN_FIELD. object and id are always kept. | | `ids` | query | 否 | string | Comma-separated ids, max 100. Batch lane: no pagination. | | `refs` | query | 否 | string | Comma-separated source:external_id, max 100. Batch lane: no pagination. | | `include_total` | query | 否 | string | true to include total. Only true or false. | | `facets` | query | 否 | string | Comma-separated facet names. Unknown token is 400 UNKNOWN_FACET. | | `sort` | query | 否 | string | Closed per-collection sort key. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | ```bash curl "https://api.nextmoe.dev/v2/catalog/persons" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/persons/{id} Get one person A person identity that groups credit names. Merged ids are 404 ENTITY_MERGED. Requires an application key. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | Decimal catalog id. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | | `view` | query | 否 | string | basic (default) or full. Closed vocabulary. | | `include` | query | 否 | string | Comma-separated blocks. Unknown token is 400 UNKNOWN_INCLUDE. | | `fields` | query | 否 | string | Comma-separated top-level keys. Unknown token is 400 UNKNOWN_FIELD. object and id are always kept. | ```bash curl "https://api.nextmoe.dev/v2/catalog/persons/value" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/persons/{id}/credit-names Credit names of one person Every credited name linked to this person. Requires an application key. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | Decimal catalog id. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | | `view` | query | 否 | string | basic (default) or full. Closed vocabulary. | | `include` | query | 否 | string | Comma-separated blocks. Unknown token is 400 UNKNOWN_INCLUDE. | | `fields` | query | 否 | string | Comma-separated top-level keys. Unknown token is 400 UNKNOWN_FIELD. object and id are always kept. | ```bash curl "https://api.nextmoe.dev/v2/catalog/persons/value/credit-names" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/proposals Edit proposal history Filed proposals, newest first. proposer_uid=+state=merged with include_total=true is the per-contributor tally. This face publishes no patch and no decision note. Requires an application key. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `cursor` | query | 否 | string | Opaque keyset cursor from a prior next_cursor. Must start with cur_. | | `limit` | query | 否 | string | Page size 1-100, default 20. Values above 100 are 400 LIMIT_TOO_LARGE, not clamped. | | `view` | query | 否 | string | basic (default) or full. Closed vocabulary. | | `include` | query | 否 | string | Comma-separated blocks. amendments is the only token on this face. Unknown token is 400 UNKNOWN_INCLUDE. | | `fields` | query | 否 | string | Comma-separated top-level keys after view/include. Unknown token is 400 UNKNOWN_FIELD. object and id are always kept. | | `ids` | query | 否 | string | Comma-separated proposal ids, max 100. Batch lane: no pagination. | | `include_total` | query | 否 | string | true to include total. Only true or false. | | `sort` | query | 否 | string | filed_desc is the only key. Closed. | | `object` | query | 否 | string | Closed family filter: work, company, character, release, tag, engine, series. | | `entity_id` | query | 否 | string | Catalog id of one entity. Requires object=. | | `site` | query | 否 | string | Tenant key. Open vocabulary; unknown values match nothing. | | `proposer_uid` | query | 否 | string | The claiming site's own user id of the proposer. Not a catalog id. | | `state` | query | 否 | string | Closed: open, merged, declined, withdrawn. | ```bash curl "https://api.nextmoe.dev/v2/catalog/proposals" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/proposals/{id} One proposal Public transparency view: proposer, state, target entity and timestamps. include=amendments adds the amendment chain. Requires an application key. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | Proposal id. | | `include` | query | 否 | string | Comma-separated blocks. amendments is the only token on this face. Unknown token is 400 UNKNOWN_INCLUDE. | | `view` | query | 否 | string | basic (default) or full. full adds amendments. | | `fields` | query | 否 | string | Comma-separated top-level keys. Unknown token is 400 UNKNOWN_FIELD. | ```bash curl "https://api.nextmoe.dev/v2/catalog/proposals/value" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/redirects Entity merge feed Redirects from merged-away ids, oldest first. Keyset-paginated. object= restricts to one family. Requires an application key. ids= is not accepted. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `cursor` | query | 否 | string | Opaque keyset cursor from a prior next_cursor. Must start with cur_. | | `limit` | query | 否 | string | Page size 1-100, default 20. Values above 100 are 400 LIMIT_TOO_LARGE, not clamped. | | `view` | query | 否 | string | basic (default) or full. Closed vocabulary. | | `include` | query | 否 | string | Comma-separated blocks. Unknown token is 400 UNKNOWN_INCLUDE. | | `fields` | query | 否 | string | Comma-separated top-level keys after view/include. Unknown token is 400 UNKNOWN_FIELD. object and id are always kept. | | `ids` | query | 否 | string | Comma-separated ids, max 100. Batch lane: no pagination. | | `refs` | query | 否 | string | Comma-separated source:external_id, max 100. Batch lane: no pagination. | | `include_total` | query | 否 | string | true to include total. Only true or false. | | `facets` | query | 否 | string | Comma-separated facet names. Unknown token is 400 UNKNOWN_FACET. | | `sort` | query | 否 | string | Closed per-collection sort key. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | | `object` | query | 否 | string | Restrict to one family. Closed: work, release, character, credit_name, person, company, tag, engine. | ```bash curl "https://api.nextmoe.dev/v2/catalog/redirects" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/releases List releases Keyset-paginated dated releases, sorted by date_desc by default. Requires an application key. ids= is a batch lane and does not paginate. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `cursor` | query | 否 | string | Opaque keyset cursor from a prior next_cursor. Must start with cur_. | | `limit` | query | 否 | string | Page size 1-100, default 20. Values above 100 are 400 LIMIT_TOO_LARGE, not clamped. | | `view` | query | 否 | string | basic (default) or full. Closed vocabulary. | | `include` | query | 否 | string | Comma-separated blocks. Unknown token is 400 UNKNOWN_INCLUDE. | | `fields` | query | 否 | string | Comma-separated top-level keys after view/include. Unknown token is 400 UNKNOWN_FIELD. object and id are always kept. | | `ids` | query | 否 | string | Comma-separated ids, max 100. Batch lane: no pagination. | | `refs` | query | 否 | string | Comma-separated source:external_id, max 100. Batch lane: no pagination. | | `include_total` | query | 否 | string | true to include total. Only true or false. | | `facets` | query | 否 | string | Comma-separated facet names. Unknown token is 400 UNKNOWN_FACET. | | `sort` | query | 否 | string | Closed per-collection sort key. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | ```bash curl "https://api.nextmoe.dev/v2/catalog/releases" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/releases/{id} Get one release A catalog release. Merged ids are 404 ENTITY_MERGED. r18 parent works are 404 without nsfw=true. Requires an application key. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | Decimal catalog id. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | | `view` | query | 否 | string | basic (default) or full. Closed vocabulary. | | `include` | query | 否 | string | Comma-separated blocks. Unknown token is 400 UNKNOWN_INCLUDE. | | `fields` | query | 否 | string | Comma-separated top-level keys. Unknown token is 400 UNKNOWN_FIELD. object and id are always kept. | ```bash curl "https://api.nextmoe.dev/v2/catalog/releases/value" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/revisions Entity revision history Every merged edit, newest first by default. sort=recorded_asc walks the same collection oldest-first by id, which is the shape a mirror or a contributor tally reads with a watermark. object=+entity_id= narrows to one entity's history. Requires an application key. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `cursor` | query | 否 | string | Opaque keyset cursor from a prior next_cursor. Must start with cur_. | | `limit` | query | 否 | string | Page size 1-100, default 20. Values above 100 are 400 LIMIT_TOO_LARGE, not clamped. | | `view` | query | 否 | string | basic (default) or full. Closed vocabulary. | | `include` | query | 否 | string | Comma-separated blocks. diff is the only token. Unknown token is 400 UNKNOWN_INCLUDE. | | `fields` | query | 否 | string | Comma-separated top-level keys after view/include. Unknown token is 400 UNKNOWN_FIELD. object and id are always kept. | | `ids` | query | 否 | string | Comma-separated revision ids, max 100. Batch lane: no pagination. | | `include_total` | query | 否 | string | true to include total. Only true or false. | | `sort` | query | 否 | string | recorded_desc (default) or recorded_asc. Closed. | | `object` | query | 否 | string | Closed family filter: work, company, character, release, tag, engine, series. | | `entity_id` | query | 否 | string | Catalog id of one entity. Requires object=. | | `site` | query | 否 | string | Tenant key. Open vocabulary; unknown values match nothing. | | `actor_uid` | query | 否 | string | The claiming site's own user id of the actor. Not a catalog id. | ```bash curl "https://api.nextmoe.dev/v2/catalog/revisions" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/revisions/{id} One revision include=diff adds the field-level change set against diff_base, or against the preceding revision when diff_base is absent. This id is what POST /v2/moderation/reverts takes. Requires an application key. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | Revision id. | | `include` | query | 否 | string | Comma-separated blocks. diff is the only token. Unknown token is 400 UNKNOWN_INCLUDE. | | `view` | query | 否 | string | basic (default) or full. full adds diff. | | `fields` | query | 否 | string | Comma-separated top-level keys. Unknown token is 400 UNKNOWN_FIELD. | | `diff_base` | query | 否 | string | Revision id to diff against. Requires include=diff. Absent means the preceding revision of the same entity. | ```bash curl "https://api.nextmoe.dev/v2/catalog/revisions/value" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/schemas/{object} Editable-field schema for one family Unauthenticated metadata: include tokens, FULL_SET, and editing-engine fields. include/full_set describe the family's DETAIL face and list_include/list_full_set its collection face, which is narrower on work. Actor capabilities are not evaluated. Unknown object is 404 NOT_FOUND. schemas/release sets creation_disabled. - 所属 API:Public API v2(/v2) - 鉴权:无需凭据 - scope:无需凭据 | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `object` | path | 是 | string | Family this schema describes. Unknown family is 404 NOT_FOUND. 取值:work \| company \| character \| release \| tag \| engine \| series | ```bash curl "https://api.nextmoe.dev/v2/catalog/schemas/work" ``` ### GET /v2/catalog/search Search catalog entities Cross-entity search. object= selects the family. Hits are search_result rows with target_object. Requires an application key. cursor= and ids= are not accepted. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `cursor` | query | 否 | string | Opaque keyset cursor from a prior next_cursor. Must start with cur_. | | `limit` | query | 否 | string | Page size 1-100, default 20. Values above 100 are 400 LIMIT_TOO_LARGE, not clamped. | | `view` | query | 否 | string | basic (default) or full. Closed vocabulary. | | `include` | query | 否 | string | Comma-separated blocks. Unknown token is 400 UNKNOWN_INCLUDE. | | `fields` | query | 否 | string | Comma-separated top-level keys after view/include. Unknown token is 400 UNKNOWN_FIELD. object and id are always kept. | | `ids` | query | 否 | string | Comma-separated ids, max 100. Batch lane: no pagination. | | `refs` | query | 否 | string | Comma-separated source:external_id, max 100. Batch lane: no pagination. | | `include_total` | query | 否 | string | true to include total. Only true or false. | | `facets` | query | 否 | string | Comma-separated facet names. Unknown token is 400 UNKNOWN_FACET. | | `sort` | query | 否 | string | Closed per-collection sort key. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | | `q` | query | 否 | string | Search string. Empty runs a popularity-ordered listing of that family. | | `object` | query | 否 | string | Required family: work, character, credit_name, company, tag. | | `locale` | query | 否 | string | zh or ja. Ignored for works. Must not be used as a discriminant. | ```bash curl "https://api.nextmoe.dev/v2/catalog/search" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/series List series Keyset-paginated series. Requires an application key. ids= is a batch lane and does not paginate. refs= is not resolved: series has no catalog_external_ref entity_type. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `cursor` | query | 否 | string | Opaque keyset cursor from a prior next_cursor. Must start with cur_. | | `limit` | query | 否 | string | Page size 1-100, default 20. Values above 100 are 400 LIMIT_TOO_LARGE, not clamped. | | `view` | query | 否 | string | basic (default) or full. Closed vocabulary. | | `include` | query | 否 | string | Comma-separated blocks. Unknown token is 400 UNKNOWN_INCLUDE. | | `fields` | query | 否 | string | Comma-separated top-level keys after view/include. Unknown token is 400 UNKNOWN_FIELD. object and id are always kept. | | `ids` | query | 否 | string | Comma-separated ids, max 100. Batch lane: no pagination. | | `refs` | query | 否 | string | Comma-separated source:external_id, max 100. Batch lane: no pagination. | | `include_total` | query | 否 | string | true to include total. Only true or false. | | `facets` | query | 否 | string | Comma-separated facet names. Unknown token is 400 UNKNOWN_FACET. | | `sort` | query | 否 | string | Closed per-collection sort key. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | ```bash curl "https://api.nextmoe.dev/v2/catalog/series" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/series/{id} Get one series Series detail. has_nsfw reports whether any member work sits behind the r18 display gate. include=intros,refs adds the corresponding blocks. Requires an application key. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | Decimal catalog id. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | | `view` | query | 否 | string | basic (default) or full. Closed vocabulary. | | `include` | query | 否 | string | Comma-separated blocks. Unknown token is 400 UNKNOWN_INCLUDE. | | `fields` | query | 否 | string | Comma-separated top-level keys. Unknown token is 400 UNKNOWN_FIELD. object and id are always kept. | ```bash curl "https://api.nextmoe.dev/v2/catalog/series/value" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/stats Catalog totals Live entity counts. Unauthenticated. - 所属 API:Public API v2(/v2) - 鉴权:无需凭据 - scope:无需凭据 无参数。 ```bash curl "https://api.nextmoe.dev/v2/catalog/stats" ``` ### GET /v2/catalog/tags List tags Keyset-paginated canonical tags. Requires an application key. ids=/refs= is a batch lane and does not paginate. has_works=true keeps only tags with works visible under the same nsfw gate. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `cursor` | query | 否 | string | Opaque keyset cursor from a prior next_cursor. Must start with cur_. | | `limit` | query | 否 | string | Page size 1-100, default 20. Values above 100 are 400 LIMIT_TOO_LARGE, not clamped. | | `view` | query | 否 | string | basic (default) or full. Closed vocabulary. | | `include` | query | 否 | string | Comma-separated blocks. Unknown token is 400 UNKNOWN_INCLUDE. | | `fields` | query | 否 | string | Comma-separated top-level keys after view/include. Unknown token is 400 UNKNOWN_FIELD. object and id are always kept. | | `ids` | query | 否 | string | Comma-separated ids, max 100. Batch lane: no pagination. | | `refs` | query | 否 | string | Comma-separated source:external_id, max 100. Batch lane: no pagination. | | `include_total` | query | 否 | string | true to include total. Only true or false. | | `facets` | query | 否 | string | Comma-separated facet names. Unknown token is 400 UNKNOWN_FACET. | | `sort` | query | 否 | string | Closed per-collection sort key. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | | `has_works` | query | 否 | string | true keeps only tags whose work_count is > 0 under the same nsfw gate. Only true or false. Absent = every tag. | ```bash curl "https://api.nextmoe.dev/v2/catalog/tags" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/tags/{id} Get one tag Canonical tag. include=intros adds the per-language tag descriptions. Requires an application key. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | Decimal catalog id. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | | `view` | query | 否 | string | basic (default) or full. Closed vocabulary. | | `include` | query | 否 | string | Comma-separated blocks. Unknown token is 400 UNKNOWN_INCLUDE. | | `fields` | query | 否 | string | Comma-separated top-level keys. Unknown token is 400 UNKNOWN_FIELD. object and id are always kept. | ```bash curl "https://api.nextmoe.dev/v2/catalog/tags/value" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/traits List traits Keyset-paginated character traits. Requires an application key. ids= is a batch lane. refs= is not resolved: traits have no catalog_external_ref entity_type. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `cursor` | query | 否 | string | Opaque keyset cursor from a prior next_cursor. Must start with cur_. | | `limit` | query | 否 | string | Page size 1-100, default 20. Values above 100 are 400 LIMIT_TOO_LARGE, not clamped. | | `view` | query | 否 | string | basic (default) or full. Closed vocabulary. | | `include` | query | 否 | string | Comma-separated blocks. Unknown token is 400 UNKNOWN_INCLUDE. | | `fields` | query | 否 | string | Comma-separated top-level keys after view/include. Unknown token is 400 UNKNOWN_FIELD. object and id are always kept. | | `ids` | query | 否 | string | Comma-separated ids, max 100. Batch lane: no pagination. | | `refs` | query | 否 | string | Comma-separated source:external_id, max 100. Batch lane: no pagination. | | `include_total` | query | 否 | string | true to include total. Only true or false. | | `facets` | query | 否 | string | Comma-separated facet names. Unknown token is 400 UNKNOWN_FACET. | | `sort` | query | 否 | string | Closed per-collection sort key. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | ```bash curl "https://api.nextmoe.dev/v2/catalog/traits" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/traits/{id} Get one trait A character-trait vocabulary row. Requires an application key. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | Decimal catalog id. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | | `view` | query | 否 | string | basic (default) or full. Closed vocabulary. | | `include` | query | 否 | string | Comma-separated blocks. Unknown token is 400 UNKNOWN_INCLUDE. | | `fields` | query | 否 | string | Comma-separated top-level keys. Unknown token is 400 UNKNOWN_FIELD. object and id are always kept. | ```bash curl "https://api.nextmoe.dev/v2/catalog/traits/value" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/works List catalog works Keyset-paginated work collection. q= switches to search (sort defaults to relevance). company_id=/tag_id=/series_id= filter the live registry when q= is absent. Requires an application key. view/include/fields/ids/refs/facets follow the v2 collection contract. include=titles,refs,intros,covers,companies,ratings,tags,credits fills on every lane; view=full is all of them except credits, which is an explicit ask. On a collection lane titles elects latin/localized and covers elects the two cover slots that grade the base cover — the full titles[] and covers[] arrays, and relations/releases/popularity/playtimes/series/platforms/screenshots/characters/engines/links, are per-record blocks and live on /v2/catalog/works/{id} and its sub-resources; asking for one here is 400 UNKNOWN_INCLUDE. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `cursor` | query | 否 | string | Opaque keyset cursor from a prior next_cursor. Must start with cur_. | | `limit` | query | 否 | string | Page size 1-100, default 20. Values above 100 are 400 LIMIT_TOO_LARGE, not clamped. | | `view` | query | 否 | string | basic (default) or full. Closed vocabulary. | | `include` | query | 否 | string | Comma-separated blocks. Unknown token is 400 UNKNOWN_INCLUDE. | | `fields` | query | 否 | string | Comma-separated top-level keys after view/include. Unknown token is 400 UNKNOWN_FIELD. object and id are always kept. | | `ids` | query | 否 | string | Comma-separated ids, max 100. Batch lane: no pagination. | | `refs` | query | 否 | string | Comma-separated source:external_id, max 100. Batch lane: no pagination. | | `include_total` | query | 否 | string | true to include total. Only true or false. | | `facets` | query | 否 | string | Comma-separated facet names. Unknown token is 400 UNKNOWN_FACET. | | `sort` | query | 否 | string | Closed per-collection sort key. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | | `q` | query | 否 | string | Work title search. Switches this collection to the search index; sort defaults to relevance. Must not be used as a discriminant. | | `content_rating` | query | 否 | string | Closed: all_ages, sensitive, r18. r18 requires nsfw=true. | | `claimed` | query | 否 | string | true or false. Absent = no gate. | | `claim_state` | query | 否 | string | Comma-separated closed states: none, live, draft. pending, declined and hidden are the per-site moderation queue: they need a key holding claim_events:read and a site= naming the caller's own site, and are otherwise not in the vocabulary. | | `content_limit` | query | 否 | string | Comma-separated closed editorial axis: sfw, nsfw. | | `site` | query | 否 | string | Claiming site key. Open vocabulary; unknown values match nothing. | | `owner_uid` | query | 否 | string | The claiming site's own user id of the claim owner. Requires site=. Live registry filter; cannot be combined with q= or search sorts. | | `company_id` | query | 否 | string | Catalog company id. Live registry filter when q= is absent. | | `company_rollup` | query | 否 | string | true expands company_id one hop down imprint/subsidiary. Only true or false. | | `tag_id` | query | 否 | string | Comma-separated canonical tag ids, AND, max 10. | | `series_id` | query | 否 | string | Catalog series id. | | `engine_id` | query | 否 | string | Catalog engine id. | | `platform` | query | 否 | string | Open vocabulary platform token. Unknown matches nothing. | | `released_after` | query | 否 | string | YYYY-MM-DD inclusive, earliest release per work. | | `released_before` | query | 否 | string | YYYY-MM-DD inclusive, earliest release per work. | | `olang` | query | 否 | string | Comma-separated BCP-47, or all. Open vocabulary; unknown values match nothing. Absent = no language gate. | ```bash curl "https://api.nextmoe.dev/v2/catalog/works" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/works/{id} Get one catalog work Work detail. spoiler=none|minor|major is the ceiling of the tags block and defaults to none. Merged ids are 404 ENTITY_MERGED with Link rel=canonical. r18 is 404 without nsfw=true. Requires an application key. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | Decimal catalog id. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | | `view` | query | 否 | string | basic (default) or full. Closed vocabulary. | | `include` | query | 否 | string | Comma-separated blocks. Unknown token is 400 UNKNOWN_INCLUDE. | | `fields` | query | 否 | string | Comma-separated top-level keys. Unknown token is 400 UNKNOWN_FIELD. object and id are always kept. | | `spoiler` | query | 否 | string | Spoiler ceiling for the tags block: none (default), minor or major. Closed vocabulary; an unknown value is 400. Tag rows above the ceiling are not returned. Only the VNDB-derived tag vocabulary carries a spoiler level — Bangumi and DLsite folksonomy publish no spoiler concept, so those rows read none — and the default is the safe ceiling. | ```bash curl "https://api.nextmoe.dev/v2/catalog/works/value" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/works/{id}/characters List characters of one work Roster characters. Same items as include=characters. Requires an application key. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | Decimal catalog work id. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | | `cursor` | query | 否 | string | Opaque keyset cursor from a prior next_cursor. Must start with cur_. | | `limit` | query | 否 | string | Page size 1-100, default 20. | ```bash curl "https://api.nextmoe.dev/v2/catalog/works/value/characters" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/works/{id}/covers List covers of one work Work cover rows. Same items as include=covers. Requires an application key. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | Decimal catalog work id. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | | `cursor` | query | 否 | string | Opaque keyset cursor from a prior next_cursor. Must start with cur_. | | `limit` | query | 否 | string | Page size 1-100, default 20. | ```bash curl "https://api.nextmoe.dev/v2/catalog/works/value/covers" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/works/{id}/credits List credits of one work Credits grouped by role. Same items as include=credits. Requires an application key. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | Decimal catalog work id. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | | `cursor` | query | 否 | string | Opaque keyset cursor from a prior next_cursor. Must start with cur_. | | `limit` | query | 否 | string | Page size 1-100, default 20. | ```bash curl "https://api.nextmoe.dev/v2/catalog/works/value/credits" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/works/{id}/engines List engines of one work Engines. Same items as include=engines. Requires an application key. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | Decimal catalog work id. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | | `cursor` | query | 否 | string | Opaque keyset cursor from a prior next_cursor. Must start with cur_. | | `limit` | query | 否 | string | Page size 1-100, default 20. | ```bash curl "https://api.nextmoe.dev/v2/catalog/works/value/engines" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/works/{id}/intros List intros of one work Intros. Same items as include=intros. Requires an application key. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | Decimal catalog work id. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | | `cursor` | query | 否 | string | Opaque keyset cursor from a prior next_cursor. Must start with cur_. | | `limit` | query | 否 | string | Page size 1-100, default 20. | ```bash curl "https://api.nextmoe.dev/v2/catalog/works/value/intros" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/works/{id}/links List links of one work Outbound links. Same items as include=links. Requires an application key. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | Decimal catalog work id. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | | `cursor` | query | 否 | string | Opaque keyset cursor from a prior next_cursor. Must start with cur_. | | `limit` | query | 否 | string | Page size 1-100, default 20. | ```bash curl "https://api.nextmoe.dev/v2/catalog/works/value/links" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/works/{id}/ratings List ratings of one work Source ratings. Same items as include=ratings. Requires an application key. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | Decimal catalog work id. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | | `cursor` | query | 否 | string | Opaque keyset cursor from a prior next_cursor. Must start with cur_. | | `limit` | query | 否 | string | Page size 1-100, default 20. | ```bash curl "https://api.nextmoe.dev/v2/catalog/works/value/ratings" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/works/{id}/relations List relations of one work Related works. Same items as include=relations. Requires an application key. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | Decimal catalog work id. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | | `cursor` | query | 否 | string | Opaque keyset cursor from a prior next_cursor. Must start with cur_. | | `limit` | query | 否 | string | Page size 1-100, default 20. | ```bash curl "https://api.nextmoe.dev/v2/catalog/works/value/relations" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/works/{id}/releases List releases of one work Releases of this work. Same items as include=releases. Requires an application key. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | Decimal catalog work id. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | | `cursor` | query | 否 | string | Opaque keyset cursor from a prior next_cursor. Must start with cur_. | | `limit` | query | 否 | string | Page size 1-100, default 20. | ```bash curl "https://api.nextmoe.dev/v2/catalog/works/value/releases" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/works/{id}/screenshots List screenshots of one work Work screenshots. Same items as include=screenshots. Requires an application key. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | Decimal catalog work id. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | | `cursor` | query | 否 | string | Opaque keyset cursor from a prior next_cursor. Must start with cur_. | | `limit` | query | 否 | string | Page size 1-100, default 20. | ```bash curl "https://api.nextmoe.dev/v2/catalog/works/value/screenshots" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/works/{id}/series List series of one work Series memberships. Same items as include=series. Requires an application key. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | Decimal catalog work id. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | | `cursor` | query | 否 | string | Opaque keyset cursor from a prior next_cursor. Must start with cur_. | | `limit` | query | 否 | string | Page size 1-100, default 20. | ```bash curl "https://api.nextmoe.dev/v2/catalog/works/value/series" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/catalog/works/{id}/tags List tags of one work Tags attached to this work. Same items as include=tags. spoiler=none|minor|major is the ceiling of this page and defaults to none, exactly as on the work detail face. Requires an application key. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:catalog:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | Decimal catalog work id. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | | `cursor` | query | 否 | string | Opaque keyset cursor from a prior next_cursor. Must start with cur_. | | `limit` | query | 否 | string | Page size 1-100, default 20. | | `spoiler` | query | 否 | string | Spoiler ceiling for this page: none (default), minor or major. Closed vocabulary; an unknown value is 400. Tag rows above the ceiling are not returned. Only the VNDB-derived tag vocabulary carries a spoiler level — Bangumi and DLsite folksonomy publish no spoiler concept, so those rows read none — and the default is the safe ceiling. | ```bash curl "https://api.nextmoe.dev/v2/catalog/works/value/tags" \ -H "Authorization: Bearer nmk_live_" ``` ## 资讯 ### GET /v2/news List news items Published news feed. Keyset-paginated. Unauthenticated. Attribution fields are required on every item. lane=, source=, published_after= and published_before= narrow the population, and include_total= counts the narrowed one. ids= and refs= are not accepted. - 所属 API:Public API v2(/v2) - 鉴权:无需凭据 - scope:无需凭据 | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `cursor` | query | 否 | string | Opaque keyset cursor from a prior next_cursor. Must start with cur_. | | `limit` | query | 否 | string | Page size 1-100, default 20. Values above 100 are 400 LIMIT_TOO_LARGE, not clamped. | | `view` | query | 否 | string | basic (default) or full. Closed vocabulary. | | `include` | query | 否 | string | Comma-separated blocks. Unknown token is 400 UNKNOWN_INCLUDE. | | `fields` | query | 否 | string | Comma-separated top-level keys after view/include. Unknown token is 400 UNKNOWN_FIELD. object and id are always kept. | | `ids` | query | 否 | string | Comma-separated ids, max 100. Batch lane: no pagination. | | `refs` | query | 否 | string | Comma-separated source:external_id, max 100. Batch lane: no pagination. | | `include_total` | query | 否 | string | true to include total. Only true or false. | | `facets` | query | 否 | string | Comma-separated facet names. Unknown token is 400 UNKNOWN_FACET. | | `sort` | query | 否 | string | Closed per-collection sort key. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | | `lane` | query | 否 | string | Comma-separated closed lanes: news, column. Absent means both. An unknown lane is 400 UNKNOWN_ENUM_VALUE. | | `source` | query | 否 | string | Comma-separated news source keys, at most 20. Open vocabulary; an unknown key matches nothing rather than failing. | | `published_after` | query | 否 | string | Inclusive lower bound on published_at. RFC 3339 UTC ending in Z. | | `published_before` | query | 否 | string | Inclusive upper bound on published_at. RFC 3339 UTC ending in Z. | ```bash curl "https://api.nextmoe.dev/v2/news" ``` ### GET /v2/news/sources List news sources Every attributed news source. Unauthenticated. - 所属 API:Public API v2(/v2) - 鉴权:无需凭据 - scope:无需凭据 无参数。 ```bash curl "https://api.nextmoe.dev/v2/news/sources" ``` ### GET /v2/news/{id} Get one news item A published news item. Withdrawn items are 404. Unauthenticated. source and source_url are always present. - 所属 API:Public API v2(/v2) - 鉴权:无需凭据 - scope:无需凭据 | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | Decimal news item id. | ```bash curl "https://api.nextmoe.dev/v2/news/value" ``` ## 我的 ### GET /v2/me/claims List my claims Claims the bearer acted on. kind=submitted (the default) keeps the ones the bearer owns, kind=audited the ones the bearer only reviewed, kind=all everything they touched. claim_state= and site= narrow further, and site= also scopes first_acted_at/acted_count. Requires a user access token. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer <用户访问令牌> - scope:无需凭据 | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `cursor` | query | 否 | string | Opaque keyset cursor from a prior next_cursor. Must start with cur_. | | `limit` | query | 否 | string | Page size 1-100, default 20. Values above 100 are 400 LIMIT_TOO_LARGE, not clamped. | | `view` | query | 否 | string | basic (default) or full. Closed vocabulary. | | `include` | query | 否 | string | Comma-separated blocks. Unknown token is 400 UNKNOWN_INCLUDE. | | `fields` | query | 否 | string | Comma-separated top-level keys after view/include. Unknown token is 400 UNKNOWN_FIELD. object and id are always kept. | | `ids` | query | 否 | string | Comma-separated ids, max 100. Batch lane: no pagination. | | `refs` | query | 否 | string | Comma-separated source:external_id, max 100. Batch lane: no pagination. | | `include_total` | query | 否 | string | true to include total. Only true or false. | | `facets` | query | 否 | string | Comma-separated facet names. Unknown token is 400 UNKNOWN_FACET. | | `sort` | query | 否 | string | Closed per-collection sort key. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | | `claim_state` | query | 否 | string | Comma-separated closed states: none, live, draft, pending, declined, hidden. | | `kind` | query | 否 | string | submitted (default) keeps works the bearer owns, audited keeps works the bearer reviewed but does not own, all keeps everything the bearer touched. | | `site` | query | 否 | string | Claiming site key. Open vocabulary; unknown values match nothing. Also scopes first_acted_at and acted_count. | ```bash curl "https://api.nextmoe.dev/v2/me/claims" \ -H "Authorization: Bearer " ``` ### POST /v2/me/claims Submit a claim Mint or claim a work. work_id claims an existing catalog work. refs= claims the work they resolve to, or mints one from display_name when none match. site_work_id with display_name and neither work_id nor refs mints a work anchored to the site's own id. field_values carries an editing-engine work field map onto any mint lane and may be sent alone, without work_id, refs or site_work_id; it is refused with work_id, and refs that already resolve to a work answer 409 instead of dropping it. A caller holding catalog.edit.trusted mints straight to live rather than pending. Requires a user access token bound to a catalog site. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer <用户访问令牌> - scope:无需凭据 无参数。 ```bash curl -X POST "https://api.nextmoe.dev/v2/me/claims" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{}' ``` ### GET /v2/me/claims/{id} Get one of my claims id is the catalog work id. Requires a user access token. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer <用户访问令牌> - scope:无需凭据 | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | Catalog work id. | ```bash curl "https://api.nextmoe.dev/v2/me/claims/value" \ -H "Authorization: Bearer " ``` ### PATCH /v2/me/claims/{id} Move a claim the caller owns PATCH {state: live|pending|withdrawn}. live publishes a draft without review, pending submits it for review, withdrawn returns it to draft. The owner may act, and an unowned claim is adopted by its first claimant. If-Match required. Requires a user access token bound to a catalog site. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer <用户访问令牌> - scope:无需凭据 | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | Catalog work id. | | `If-Match` | header | 是 | string | Current ETag. Required; its absence is 428 PRECONDITION_REQUIRED. | ```bash curl -X PATCH "https://api.nextmoe.dev/v2/me/claims/value" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"state":"live"}' ``` ### DELETE /v2/me/claims/{id} Delete a draft claim Deletes a draft the caller owns; a live or pending claim must be withdrawn to draft first (PATCH state=withdrawn). This soft-deletes the catalog work row and writes no claim event. 204 with no body. Requires a user access token. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer <用户访问令牌> - scope:无需凭据 | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | Catalog work id. | ```bash curl -X DELETE "https://api.nextmoe.dev/v2/me/claims/value" \ -H "Authorization: Bearer " ``` ### GET /v2/me/cover-votes List my cover votes Every cover the bearer has voted up. Requires a user access token. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer <用户访问令牌> - scope:无需凭据 无参数。 ```bash curl "https://api.nextmoe.dev/v2/me/cover-votes" \ -H "Authorization: Bearer " ``` ### PUT /v2/me/cover-votes/{cover_id} Cast a cover vote Only vote=up is stored. One ballot per work. Requires a user access token. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer <用户访问令牌> - scope:无需凭据 | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `cover_id` | path | 是 | string | Catalog cover row id. | ```bash curl -X PUT "https://api.nextmoe.dev/v2/me/cover-votes/value" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"vote":"up"}' ``` ### DELETE /v2/me/cover-votes/{cover_id} Withdraw a cover vote 204 with no body. Requires a user access token. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer <用户访问令牌> - scope:无需凭据 | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `cover_id` | path | 是 | string | Catalog cover row id. | ```bash curl -X DELETE "https://api.nextmoe.dev/v2/me/cover-votes/value" \ -H "Authorization: Bearer " ``` ### POST /v2/me/edit-images Upload an image for an edit proposal multipart/form-data with preset and file. Returns the hash an edit proposal carries in a cover or screenshot row. Requires a user access token bound to a catalog site. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer <用户访问令牌> - scope:无需凭据 无参数。 ```bash curl -X POST "https://api.nextmoe.dev/v2/me/edit-images" \ -H "Authorization: Bearer " ``` ### GET /v2/me/news List my news items Items under the sources bound to the bearer, pending included. Keyset-paginated. Requires a user access token. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer <用户访问令牌> - scope:无需凭据 | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `cursor` | query | 否 | string | Opaque keyset cursor from a prior next_cursor. Must start with cur_. | | `limit` | query | 否 | string | Page size 1-100, default 20. Values above 100 are 400 LIMIT_TOO_LARGE, not clamped. | | `view` | query | 否 | string | basic (default) or full. Closed vocabulary. | | `include` | query | 否 | string | Comma-separated blocks. Unknown token is 400 UNKNOWN_INCLUDE. | | `fields` | query | 否 | string | Comma-separated top-level keys after view/include. Unknown token is 400 UNKNOWN_FIELD. object and id are always kept. | | `ids` | query | 否 | string | Comma-separated ids, max 100. Batch lane: no pagination. | | `refs` | query | 否 | string | Comma-separated source:external_id, max 100. Batch lane: no pagination. | | `include_total` | query | 否 | string | true to include total. Only true or false. | | `facets` | query | 否 | string | Comma-separated facet names. Unknown token is 400 UNKNOWN_FACET. | | `sort` | query | 否 | string | Closed per-collection sort key. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | ```bash curl "https://api.nextmoe.dev/v2/me/news" \ -H "Authorization: Bearer " ``` ### POST /v2/me/news Submit a news item Always lands on pending: publishing is a human step. source must be bound to the bearer and active. Requires a user access token. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer <用户访问令牌> - scope:无需凭据 无参数。 ```bash curl -X POST "https://api.nextmoe.dev/v2/me/news" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"source":"string","source_url":"string","summary":"string","title":"string"}' ``` ### GET /v2/me/news/{id} Get one of my news items Carries an ETag for If-Match. Requires a user access token. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer <用户访问令牌> - scope:无需凭据 | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | News item id. | ```bash curl "https://api.nextmoe.dev/v2/me/news/value" \ -H "Authorization: Bearer " ``` ### PATCH /v2/me/news/{id} Edit or withdraw one of my news items While pending, edits title/summary/source_url/banner_hash/work_ids and sends the item back for machine scoring. Once published the only legal transition is {"status":"withdrawn"} with If-Match. rejected is terminal. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer <用户访问令牌> - scope:无需凭据 | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | News item id. | | `If-Match` | header | 否 | string | Current ETag. Required to withdraw. | ```bash curl -X PATCH "https://api.nextmoe.dev/v2/me/news/value" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{}' ``` ### GET /v2/me/playtimes List my playtimes The bearer user's playtime rows. work_ids= is a batch read. Requires a user access token. Any app may call this; playtime:read is not required. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer <用户访问令牌> - scope:无需凭据 | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `cursor` | query | 否 | string | Opaque keyset cursor from a prior next_cursor. Must start with cur_. | | `limit` | query | 否 | string | Page size 1-100, default 20. Values above 100 are 400 LIMIT_TOO_LARGE, not clamped. | | `view` | query | 否 | string | basic (default) or full. Closed vocabulary. | | `include` | query | 否 | string | Comma-separated blocks. Unknown token is 400 UNKNOWN_INCLUDE. | | `fields` | query | 否 | string | Comma-separated top-level keys after view/include. Unknown token is 400 UNKNOWN_FIELD. object and id are always kept. | | `ids` | query | 否 | string | Comma-separated ids, max 100. Batch lane: no pagination. | | `refs` | query | 否 | string | Comma-separated source:external_id, max 100. Batch lane: no pagination. | | `include_total` | query | 否 | string | true to include total. Only true or false. | | `facets` | query | 否 | string | Comma-separated facet names. Unknown token is 400 UNKNOWN_FACET. | | `sort` | query | 否 | string | Closed per-collection sort key. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | | `work_ids` | query | 否 | string | Comma-separated work ids, max 100. Batch read, no pagination. | ```bash curl "https://api.nextmoe.dev/v2/me/playtimes" \ -H "Authorization: Bearer " ``` ### POST /v2/me/playtimes Batch write playtimes 207 Multi-Status. Each item is a playtime or a problem. Requires a user access token. Any app may call this; playtime:write is not required. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer <用户访问令牌> - scope:无需凭据 无参数。 ```bash curl -X POST "https://api.nextmoe.dev/v2/me/playtimes" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"items":[{"minutes":0,"work_id":"string"}]}' ``` ### GET /v2/me/playtimes/{work_id} Get my playtime on one work 404 when the user has never reported. Requires a user access token. Any app may call this; playtime:read is not required. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer <用户访问令牌> - scope:无需凭据 | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `work_id` | path | 是 | string | Catalog work id. | ```bash curl "https://api.nextmoe.dev/v2/me/playtimes/value" \ -H "Authorization: Bearer " ``` ### PUT /v2/me/playtimes/{work_id} Replace my playtime on one work Absolute minutes. Naturally idempotent. Requires a user access token. Any app may call this; playtime:write is not required. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer <用户访问令牌> - scope:无需凭据 | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `work_id` | path | 是 | string | Catalog work id. | ```bash curl -X PUT "https://api.nextmoe.dev/v2/me/playtimes/value" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"minutes":0}' ``` ### DELETE /v2/me/playtimes/{work_id} Delete my playtime on one work 204 with no body. Requires a user access token. Any app may call this; playtime:write is not required. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer <用户访问令牌> - scope:无需凭据 | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `work_id` | path | 是 | string | Catalog work id. | ```bash curl -X DELETE "https://api.nextmoe.dev/v2/me/playtimes/value" \ -H "Authorization: Bearer " ``` ### GET /v2/me/proposals List my proposals The bearer's own proposals. state= is a closed vocabulary and an unknown value is 400. object= or entity_type= narrows to one family, entity_id= to one entity — on this lane entity_id= is accepted without a family because every row already belongs to the caller. Requires a user access token. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer <用户访问令牌> - scope:无需凭据 | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `cursor` | query | 否 | string | Opaque keyset cursor from a prior next_cursor. Must start with cur_. | | `limit` | query | 否 | string | Page size 1-100, default 20. Values above 100 are 400 LIMIT_TOO_LARGE, not clamped. | | `view` | query | 否 | string | basic (default) or full. Closed vocabulary. | | `include` | query | 否 | string | Comma-separated blocks. Unknown token is 400 UNKNOWN_INCLUDE. | | `fields` | query | 否 | string | Comma-separated top-level keys after view/include. Unknown token is 400 UNKNOWN_FIELD. object and id are always kept. | | `ids` | query | 否 | string | Comma-separated ids, max 100. Batch lane: no pagination. | | `refs` | query | 否 | string | Comma-separated source:external_id, max 100. Batch lane: no pagination. | | `include_total` | query | 否 | string | true to include total. Only true or false. | | `facets` | query | 否 | string | Comma-separated facet names. Unknown token is 400 UNKNOWN_FACET. | | `sort` | query | 否 | string | Closed per-collection sort key. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | | `state` | query | 否 | string | Closed: open, pending, merged, declined, withdrawn. Unknown value is 400 UNKNOWN_ENUM_VALUE. | | `object` | query | 否 | string | Closed family filter: work, company, character, release, tag, engine, series. | | `entity_type` | query | 否 | string | Editing-engine type, e.g. catalog.work. The same spelling POST /v2/me/proposals takes in its body. Names the same filter as object=; sending both with different families is 400. | | `entity_id` | query | 否 | string | Catalog id of one entity. Accepted alone on this lane, which is already fenced to the bearer's own proposals; pair it with object= or entity_type= when ids collide across families. | ```bash curl "https://api.nextmoe.dev/v2/me/proposals" \ -H "Authorization: Bearer " ``` ### POST /v2/me/proposals File a proposal Requires a user access token bound to a catalog site. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer <用户访问令牌> - scope:无需凭据 无参数。 ```bash curl -X POST "https://api.nextmoe.dev/v2/me/proposals" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"entity_id":"string","entity_type":"string","patch":{"key":"string"}}' ``` ### GET /v2/me/proposals/{id} Get one of my proposals Requires a user access token. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer <用户访问令牌> - scope:无需凭据 | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | Proposal id. | | `include` | query | 否 | string | Comma-separated blocks: amendments, patch. Unknown token is 400 UNKNOWN_INCLUDE. | | `view` | query | 否 | string | basic (default) or full. full adds amendments and patch. | ```bash curl "https://api.nextmoe.dev/v2/me/proposals/value" \ -H "Authorization: Bearer " ``` ### PATCH /v2/me/proposals/{id} Amend or withdraw a proposal If-Match required. Requires a user access token. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer <用户访问令牌> - scope:无需凭据 | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | Proposal id. | | `If-Match` | header | 是 | string | Current ETag. Required; its absence is 428 PRECONDITION_REQUIRED. | ```bash curl -X PATCH "https://api.nextmoe.dev/v2/me/proposals/value" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{}' ``` ### POST /v2/me/proposals/{id}/amendments Append an amendment If-Match required. Requires a user access token. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer <用户访问令牌> - scope:无需凭据 | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | Proposal id. | | `If-Match` | header | 是 | string | Current ETag. Required; its absence is 428 PRECONDITION_REQUIRED. | ```bash curl -X POST "https://api.nextmoe.dev/v2/me/proposals/value/amendments" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{}' ``` ## 审核 ### GET /v2/moderation/claims Moderation claim queue Claims on the token site awaiting a decision. claim_state= selects which states the queue lists and defaults to pending; the decision face also acts on live, draft and declined (ban) and on hidden (unban), so those are listable here too. Oldest submission first. ids= and refs= are not accepted. Requires a user access token with review authority. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer <用户访问令牌> - scope:无需凭据 | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `cursor` | query | 否 | string | Opaque keyset cursor from a prior next_cursor. Must start with cur_. | | `limit` | query | 否 | string | Page size 1-100, default 20. Values above 100 are 400 LIMIT_TOO_LARGE, not clamped. | | `view` | query | 否 | string | basic (default) or full. Closed vocabulary. | | `include` | query | 否 | string | Comma-separated blocks. Unknown token is 400 UNKNOWN_INCLUDE. | | `fields` | query | 否 | string | Comma-separated top-level keys after view/include. Unknown token is 400 UNKNOWN_FIELD. object and id are always kept. | | `ids` | query | 否 | string | Comma-separated ids, max 100. Batch lane: no pagination. | | `refs` | query | 否 | string | Comma-separated source:external_id, max 100. Batch lane: no pagination. | | `include_total` | query | 否 | string | true to include total. Only true or false. | | `facets` | query | 否 | string | Comma-separated facet names. Unknown token is 400 UNKNOWN_FACET. | | `sort` | query | 否 | string | Closed per-collection sort key. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | | `claim_state` | query | 否 | string | Comma-separated closed states: live, draft, pending, declined, hidden. Default pending. hidden is how a banned claim is found for unban. | ```bash curl "https://api.nextmoe.dev/v2/moderation/claims" \ -H "Authorization: Bearer " ``` ### GET /v2/moderation/claims/{id} Get one moderation claim id is the catalog work id. Site-fenced. Requires a user access token bound to a catalog site. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer <用户访问令牌> - scope:无需凭据 | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | Catalog work id. | ```bash curl "https://api.nextmoe.dev/v2/moderation/claims/value" \ -H "Authorization: Bearer " ``` ### POST /v2/moderation/claims/{id}/decisions Decide a claim decision=approve|decline|ban|unban. unban restores the state the claim was hidden from. If-Match required, and the ETag comes from GET /v2/moderation/claims/{id}. Requires the catalog.claim.review permission. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer <用户访问令牌> - scope:无需凭据 | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | Catalog work id. | | `If-Match` | header | 是 | string | Current ETag. Required; its absence is 428 PRECONDITION_REQUIRED. | ```bash curl -X POST "https://api.nextmoe.dev/v2/moderation/claims/value/decisions" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"decision":"approve"}' ``` ### GET /v2/moderation/proposals Moderation proposal queue Open proposals on the token site. The whole queue requires a catalog review permission. object= (or entity_type=) with entity_id= narrows it to one entity, which that entity's owner may read without one — the same owner-review channel the editing engine resolves per field. entity_id= without a family is 422. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer <用户访问令牌> - scope:无需凭据 | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `cursor` | query | 否 | string | Opaque keyset cursor from a prior next_cursor. Must start with cur_. | | `limit` | query | 否 | string | Page size 1-100, default 20. Values above 100 are 400 LIMIT_TOO_LARGE, not clamped. | | `view` | query | 否 | string | basic (default) or full. Closed vocabulary. | | `include` | query | 否 | string | Comma-separated blocks. Unknown token is 400 UNKNOWN_INCLUDE. | | `fields` | query | 否 | string | Comma-separated top-level keys after view/include. Unknown token is 400 UNKNOWN_FIELD. object and id are always kept. | | `ids` | query | 否 | string | Comma-separated ids, max 100. Batch lane: no pagination. | | `refs` | query | 否 | string | Comma-separated source:external_id, max 100. Batch lane: no pagination. | | `include_total` | query | 否 | string | true to include total. Only true or false. | | `facets` | query | 否 | string | Comma-separated facet names. Unknown token is 400 UNKNOWN_FACET. | | `sort` | query | 否 | string | Closed per-collection sort key. | | `nsfw` | query | 否 | string | true includes r18. false or absent hides r18. Only true or false. | | `object` | query | 否 | string | Closed family filter: work, company, character, release, tag, engine, series. | | `entity_type` | query | 否 | string | Editing-engine type, e.g. catalog.work. Names the same filter as object=; sending both with different families is 400. | | `entity_id` | query | 否 | string | Catalog id of one entity. Requires object= or entity_type=. Narrows the queue to that entity, which its owner may read without a review permission. | ```bash curl "https://api.nextmoe.dev/v2/moderation/proposals" \ -H "Authorization: Bearer " ``` ### GET /v2/moderation/proposals/{id} Get one moderation proposal Site-fenced. include=patch adds the proposed and effective patches a decision is taken on. The ETag is the validator POST /v2/moderation/proposals/{id}/decisions takes as If-Match. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer <用户访问令牌> - scope:无需凭据 | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | Proposal id. | | `include` | query | 否 | string | Comma-separated blocks: amendments, patch. Unknown token is 400 UNKNOWN_INCLUDE. | | `view` | query | 否 | string | basic (default) or full. full adds amendments and patch. | ```bash curl "https://api.nextmoe.dev/v2/moderation/proposals/value" \ -H "Authorization: Bearer " ``` ### POST /v2/moderation/proposals/{id}/decisions Decide a proposal decision=merge|decline. If-Match required. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer <用户访问令牌> - scope:无需凭据 | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | Proposal id. | | `If-Match` | header | 是 | string | Current ETag. Required; its absence is 428 PRECONDITION_REQUIRED. | ```bash curl -X POST "https://api.nextmoe.dev/v2/moderation/proposals/value/decisions" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"decision":"merge"}' ``` ### POST /v2/moderation/reverts Revert to a revision Body names revision_id. Requires a user access token bound to a catalog site. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer <用户访问令牌> - scope:无需凭据 无参数。 ```bash curl -X POST "https://api.nextmoe.dev/v2/moderation/reverts" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"revision_id":"string"}' ``` ### GET /v2/moderation/snapshots/{object}/{id} Current edit snapshot Registered field values. Requires a user access token. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer <用户访问令牌> - scope:无需凭据 | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `object` | path | 是 | string | Family: work, company, character, release, tag, engine, series. | | `id` | path | 是 | string | Catalog id. | ```bash curl "https://api.nextmoe.dev/v2/moderation/snapshots/value/value" \ -H "Authorization: Bearer " ``` ## 商店 ### GET /v2/store/prices Get storefront prices for many works Cached storefront price quotes for up to 100 catalog works. Unauthenticated. Quotes are cached observations; read fetched_at / expires_at / stale. pending means a fetch is in flight, retry shortly. The batch lane never waits on a cold miss. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:无需凭据 | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `ids` | query | 是 | string | Comma-separated decimal catalog work ids, at most 100. Duplicates are collapsed. Unknown ids are listed in missing[]. | ```bash curl "https://api.nextmoe.dev/v2/store/prices" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/store/prices/{id} Get storefront prices for one work Cached storefront price quotes for one catalog work. Unauthenticated. Quotes are cached observations; read fetched_at / expires_at / stale. pending means a fetch is in flight, retry shortly. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:无需凭据 | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `id` | path | 是 | string | Decimal catalog work id. | ```bash curl "https://api.nextmoe.dev/v2/store/prices/value" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/store/purchase-links/{product_id} Get purchase and coupon links for one product Mints (or returns) the short links this application sends readers to. The purchase link belongs to your application alone — use it verbatim, because the click counter behind it is what settlement reads. coupon_url and campaign are present only while a campaign is running. Requires an application key with the store:read scope. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:store:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `product_id` | path | 是 | string | DLsite product number. RJ is doujin, VJ is commercial. | ```bash curl "https://api.nextmoe.dev/v2/store/purchase-links/value" \ -H "Authorization: Bearer nmk_live_" ``` ### GET /v2/store/stats Click statistics for my links Daily clicks on the links this application minted, over a JST-day range of at most 92 days. The bearer application is the subject — this replaces v1's /v1/store/me/stats. Requires an application key with the store:read scope. - 所属 API:Public API v2(/v2) - 鉴权:Authorization: Bearer nmk_live_… - scope:store:read | 参数 | 位置 | 必填 | 类型 | 说明 | | --- | --- | --- | --- | --- | | `from` | query | 否 | string | First JST day to cover, YYYY-MM-DD. Optional; defaults to 29 days before to. | | `to` | query | 否 | string | Last JST day to cover, YYYY-MM-DD. Optional; defaults to today. from and to must be at most 92 days apart. | ```bash curl "https://api.nextmoe.dev/v2/store/stats" \ -H "Authorization: Bearer nmk_live_" ``` --- # AI / MCP 接入 NextMoe 开放 API 同时以 MCP(Model Context Protocol)server 暴露:端点 https://mcp.nextmoe.dev/mcp,Streamable HTTP、stateless,带上同一把 API 密钥即可。它是一层纯透传适配——每次工具调用就是一次对公开 /v2 GET 的请求,鉴权、限流、配额与用量与直连毫无区别。工具名就是 OpenAPI operationId,清单由 cmd/gen-v2-portal 从同一份 v2 spec 生成。 ## 工具(55 个) - `getCatalogCharacter` `GET /v2/catalog/characters/{id}`:按 id 取角色;view=full 追加性别、生日、三围、血型与 instance_of_id。nsfw 同时控 r18 作品与 sexual 系 traits 的可见性。 - `getCatalogCharacterAppearances` `GET /v2/catalog/characters/{id}/appearances`:一个角色出演的全部作品,各带 roster_role、剧透等级与配音署名,offset 游标分页。 - `getCatalogCompany` `GET /v2/catalog/companies/{id}`:按 id 取公司 / 厂牌 / 社团注册行(v1 的 labels)。被合并的 id 返回 404 ENTITY_MERGED。 - `getCatalogCompanyGraph` `GET /v2/catalog/companies/{id}/graph`:一次拿到一家公司周围的整个会社家族(母公司 / 子品牌 / 文库 / 继承),nodes[] + edges[] 有向。getCatalogCompany 的关联只有一跳,问「某社旗下有哪些牌子」用这个;反向边按设计不下发。 - `getCatalogCreditName` `GET /v2/catalog/credit-names/{id}`:按 id 取一个署名(credit name)——这是名字不是人;尚未挂到人物身份上时 person_id 为 null。 - `getCatalogCreditNameCredits` `GET /v2/catalog/credit-names/{id}/credits`:一个署名被记在哪些作品上,offset 游标分页——问「这个名字做过什么」用它。 - `getCatalogEngine` `GET /v2/catalog/engines/{id}`:按 id 取引擎记录(名称与跨源 refs)。 - `getCatalogPerson` `GET /v2/catalog/persons/{id}`:按 id 取人物身份:它把同一个人用过的多个署名聚成一个人格。被合并的 id 返回 404 ENTITY_MERGED。 - `getCatalogPersonCreditNames` `GET /v2/catalog/persons/{id}/credit-names`:一个人物身份名下的全部署名。人物与署名是两层:身份在这里,某个署名各自的作品在 getCatalogCreditNameCredits。 - `getCatalogProposal` `GET /v2/catalog/proposals/{id}`:按 id 取单条提案的公开透明视图:提案人、状态、目标实体与时间戳;include=amendments 追加修订链。 - `getCatalogRelease` `GET /v2/catalog/releases/{id}`:按 id 取单条发售行。被合并的 id 返回 404 ENTITY_MERGED;母作品是 r18 时,不带 nsfw=true 也是 404。 - `getCatalogRevision` `GET /v2/catalog/revisions/{id}`:按 id 取单条修订;include=diff 追加相对 diff_base(缺省为前一条)的字段级变更集。 - `getCatalogSchema` `GET /v2/catalog/schemas/{object}`(无需密钥):一个实体家族的可编辑字段 schema 与 include 令牌全集(含 FULL_SET)。无需凭据,且不评估调用方权限——它描述的是形状,不是许可。 - `getCatalogSeries` `GET /v2/catalog/series/{id}`:按 id 取系列(身份、源锚与简介);成员作品用 listCatalogWorks 的 series_id= 取——回答「这个系列按什么顺序玩」。 - `getCatalogStats` `GET /v2/catalog/stats`(无需密钥):全库计数:各家族 LIVE 实体总量。无参数,无需凭据。 - `getCatalogTag` `GET /v2/catalog/tags/{id}`:按 id 取正典标签——跨源标签词表的一行。 - `getCatalogTrait` `GET /v2/catalog/traits/{id}`:按 id 取角色特征词表的一行。 - `getCatalogWork` `GET /v2/catalog/works/{id}`:按 id 取作品注册行,include=credits,relations 并取子块。被合并的 id 返回 404 ENTITY_MERGED 并在 Link rel=canonical 里给继任者;r18 不带 nsfw=true 同样是 404。 - `getCatalogWorkCharacters` `GET /v2/catalog/works/{id}/characters`:作品的角色花名册块(单块 cursor 分页,带 roster_role 与配音署名)。此面不设剧透上限——每行自带 spoiler 等级,由调用方分级。 - `getCatalogWorkCovers` `GET /v2/catalog/works/{id}/covers`:作品的封面块(单块 cursor 分页,与 include=covers 同一批行)。只要这一块时优先于 getCatalogWork;CDN 渲染不了的行在分页前就被丢掉,所以短页是「块取完了」而不是「被过滤了」。 - `getCatalogWorkCredits` `GET /v2/catalog/works/{id}/credits`:作品的职员署名块,按 role 分组。分页按署名行不按组:跨页的 role 会在两页各出现一次、各带该页的切片,拼页时要按 role 合并组。 - `getCatalogWorkEngines` `GET /v2/catalog/works/{id}/engines`:作品的引擎块(单块 cursor 分页,与 include=engines 同一批行)。 - `getCatalogWorkIntros` `GET /v2/catalog/works/{id}/intros`:作品的简介块(单块 cursor 分页,一语言一行)。源写的胜过机翻的,机翻行是打标不是隐藏——把它引用成「官方说法」前先看标。 - `getCatalogWorkLinks` `GET /v2/catalog/works/{id}/links`:作品的非身份外链块(官网 / Steam / X 等,单块 cursor 分页)。这些是地址不是锚,身份锚在 getCatalogWork 的 refs[];dlsite / dmm 这类无法由裸 code 还原商店 URL 的源按设计不在此面。 - `getCatalogWorkRatings` `GET /v2/catalog/works/{id}/ratings`:作品的分源评分块(单块 cursor 分页,带完整投票直方图与离散度)。分数留在各源原生标尺,永不混算成一个数。 - `getCatalogWorkRelations` `GET /v2/catalog/works/{id}/relations`:作品的关联作品块(单块 cursor 分页)。不带 nsfw 时 r18 关联端是整条丢弃而非置空,页里数的是幸存行。 - `getCatalogWorkReleases` `GET /v2/catalog/works/{id}/releases`:作品的发售行块(单块 cursor 分页,各带源锚与自己的发行公司——移植版 / 英文版各自的发行商在这里)。跨作品的发售时间线用 listCatalogReleases。 - `getCatalogWorkScreenshots` `GET /v2/catalog/works/{id}/screenshots`:作品的截图块(单块 cursor 分页,带尺寸与 thumbhash)。每行各自带 sexual / violence 等级——此面只报告不过滤,渲染门由调用方自己把。 - `getCatalogWorkSeries` `GET /v2/catalog/works/{id}/series`:作品所属系列块(单块 cursor 分页)。member_count 是系列的全部成员数而非本页——顺着它用 listCatalogWorks 的 series_id= 或 getCatalogSeries。 - `getCatalogWorkTags` `GET /v2/catalog/works/{id}/tags`:作品的源标签块(单块 cursor 分页)。命中正典映射的行带 canonical id 与 tier / kind,未映射的行不带。 - `getNewsItem` `GET /v2/news/{id}`(无需密钥):按 id 取单条资讯。已撤回的、上游原文已消失的条目返回 404——这是契约不是查不到,别重试,也别拿缓存副本顶上。 - `getProblemType` `GET /v2/problems/{code}`(无需密钥):按 code 取单条错误码定义。未知 code 返回 404 而不是 422——路径参数是查找键,不是封闭枚举。 - `getVocabulary` `GET /v2/vocabularies/{name}`(无需密钥):按 name 取一个词表的全部已发布取值。未知 name 返回 404。 - `listCatalogCalendar` `GET /v2/catalog/calendar`:发售月历:month= / year= 选窗口,precision= 与 status= 在「已定档到月」「只知年」「已公布未定档」三个视图间切换——v1 的三条月历路径在 v2 是这一条。不接受 ids=。 - `listCatalogChanges` `GET /v2/catalog/changes`:增量同步变更流:近期更新过的作品,最旧优先。存下 next_cursor,下次轮询只拿变化的部分。 - `listCatalogCharacters` `GET /v2/catalog/characters`:keyset 分页浏览角色注册表。ids=/refs= 是批量水合泳道,不分页。 - `listCatalogClaimEvents` `GET /v2/catalog/claim-events`:认领生命周期事件流,默认最新在前;sort=recorded_asc 是镜像 / 发奖 cron 按 id 水位续读的那条泳道。事件带驳回理由与操作者 uid,所以除 catalog:read 外还要运营发放的 claim_events:read。 - `listCatalogCompanies` `GET /v2/catalog/companies`:浏览公司 / 厂牌词表本身(v1 的 labels)——用来发现 company id 再喂给 listCatalogWorks 的 company_id=。 - `listCatalogCreditNames` `GET /v2/catalog/credit-names`:keyset 分页浏览署名注册表,q= 按名字过滤。ids=/refs= 是批量水合泳道,不分页。 - `listCatalogEngines` `GET /v2/catalog/engines`:浏览引擎词表本身——用来发现 engine id 再喂给 listCatalogWorks 的 engine_id=。 - `listCatalogPersons` `GET /v2/catalog/persons`:keyset 分页浏览人物身份注册表。ids=/refs= 是批量水合泳道,不分页。 - `listCatalogProposals` `GET /v2/catalog/proposals`:已提交的编辑提案流,最新优先。proposer_uid= + state=merged + include_total=true 就是按贡献者的合入计数。此面不下发 patch,也不下发审核意见。 - `listCatalogRedirects` `GET /v2/catalog/redirects`:合并去向流:被合并掉的 id 指向哪个继任者,最旧优先。存下游标增量消费,就能把本地副本里的死 id 换成活的。object= 收敛到单个家族;不接受 ids=。 - `listCatalogReleases` `GET /v2/catalog/releases`:发售动态的 release 粒度:每一条发售行各自成项,移植版 / 复刻 / 中文化都看得见(月历只把作品放在最早发售月且只显示一次)。缺省按日期倒序。 - `listCatalogRevisions` `GET /v2/catalog/revisions`:已合入的编辑修订流,缺省最新优先;sort=recorded_asc 按 id 从旧到新走同一个集合,这是镜像与贡献统计该用的姿态(配一条水位线)。object= + entity_id= 收敛到单个实体的历史。 - `listCatalogSeries` `GET /v2/catalog/series`:浏览系列词表本身。系列不进搜索索引,这是发现 series id 的唯一入口;refs= 对它不解析:系列没有外部锚类型。 - `listCatalogTags` `GET /v2/catalog/tags`:浏览正典标签词表本身——用来发现 tag id 再喂给 listCatalogWorks 的 tag_id=。 - `listCatalogTraits` `GET /v2/catalog/traits`:浏览角色特征词表本身——用来发现 trait id。refs= 对它不解析:特征没有外部锚类型。 - `listCatalogWorks` `GET /v2/catalog/works`:浏览 / 过滤作品注册表(评级 / 公司 / 标签 / 系列 / 引擎 / 平台 / 发售窗,keyset 分页,ids=/refs= 批量水合)。带 q= 时整条转为检索并按相关度排序——「查询 + 过滤」与「纯过滤」在 v2 是同一条路径。 - `listNews` `GET /v2/news`(无需密钥):合作媒体的 Galgame 资讯索引(keyset 分页,无需凭据)。只有标题、摘要与题图,正文永不下发——每条恒带来源与 source_url,读全文要回到媒体自己的站点。 - `listNewsSources` `GET /v2/news/sources`(无需密钥):资讯来源注册表:每家媒体的名称、主页、专栏入口,以及该渲染的归属文案。无参数,无需凭据。 - `listProblemReasons` `GET /v2/problems/reasons`(无需密钥):字段级 reason 的封闭清单,无需凭据。这里的取值永远不会作为顶层 code 出现。 - `listProblemTypes` `GET /v2/problems`(无需密钥):错误码注册表:/v2 全部顶层 code 的封闭清单,keyset 分页,无需凭据。 - `listVocabularies` `GET /v2/vocabularies`(无需密钥):已发布词表清单(封闭词表与 seed-open 词表),keyset 分页,无需凭据。 - `searchCatalog` `GET /v2/catalog/search`:按名字跨家族搜身份图谱:object= 选家族(works / characters / credit-names / companies …),命中行是 search_result,各带 target_object 说明它是什么。r18 需 nsfw=true;不接受 cursor= 与 ids=。