鉴权与凭据

读目录可以什么凭据都不带。要带的话,一条请求只带一个;带哪一个,由你读的是「目录里的公共事实」还是「某个用户自己的东西」决定。

三种身份

身份 请求头 代表谁 用在哪些前缀
匿名 不带 任何人 /v2/catalog 只读面(claim-events 除外,按 IP 限流);/v2/news、/v2/vocabularies、/v2/problems、/v2/catalog/stats、/v2/catalog/schemas/{object}
应用密钥 Authorization: Bearer nmk_live_… 你的应用 /v2/catalog、/v2/store、/v2/moyu/*、/v2/sticker/*
用户访问令牌 Authorization: Bearer <access_token> 授权给你的那个用户 /v2/me、/v2/moderation;带 catalog:read 时也可用于 /v2/catalog

一条请求只带一个凭据。/v2/catalog 收两种凭据,意思是二选一,不是两个都带。服务端按 Authorization 里那一个值的前缀分道:nmk_ 是应用密钥,其余按用户令牌解析;一种失败了不会再当另一种试一次。

不带凭据

/v2/catalog 只读面(/v2/catalog/claim-events 除外)不带 Authorization 也能调。返回的数据和带密钥时逐字节相同,nsfw=true 照样可用;差别只在额度:匿名请求按来源 IPv4 地址或 IPv6 /64 共用一份很小的额度(默认每分钟 30 次、每天 1,000 次,见 限流与配额)。

它是给个人脚本、浏览器扩展和纯前端小项目准备的——这些场景要么没有服务端可放密钥,要么量小到不值得注册。CORS 对所有来源开放,浏览器里直接 fetch 就行,不要为此把密钥写进前端。

只有完全不带 Authorization 头才算匿名。带了却无效、过期或已吊销的凭据在 /v2/catalog 上仍然是 401 INVALID_CREDENTIAL,不会悄悄降级成匿名——否则密钥一失效,你的服务就会在毫无报错的情况下掉进那份很小的匿名额度。同理,只带 X-API-Key 头也不算匿名:/v2 不读这个头,仍回 401 MISSING_CREDENTIAL。

应用密钥

  • 前缀 nmk_live_(生产)或 nmk_test_(开发联调),尾部带 CRC32 校验位——手抖改错一个字符能在到达服务端前就被认出来。
  • 只在铸造时显示一次。丢了就吊销重铸,没有找回。
  • 它是机密:只放服务端。浏览器、移动端二进制、公开仓库、CI 日志都不行。前端要用数据,请让自己的后端代理;分发出去的桌面客户端没有后端可代理,改用用户访问令牌(见下)。
  • 每个应用最多 5 把在用密钥——轮换时先铸新的、灰度切流、再吊销旧的,不必停机。

scope

scope 开什么 怎么拿
catalog:read 整个 /v2/catalog 只读面 控制台自助勾选;也可作为用户 scope 向人索取
store:read /v2/store 商店联盟链接与统计 控制台自助勾选
claim_events:read 认领事件 feed 与自家站点的审核队列状态(claim_state=pending,declined,hidden) 运营方按需授予,不能自助

claim_events:read 不开放自助是有原因的:那条 feed 里带着每次拒绝的理由和做出决定的审核员 uid。

/v2/moyu/* 与 /v2/sticker/* 两个下游面(面联邦,gateway-terminated)不需要任何 scope——任意有效应用密钥都能调用,网关只看密钥本身与限流。它们也不支持浏览器直接 fetch:预检 OPTIONS 不带认证头,会被网关 401。这不是你的 CORS 配置问题——密钥本就不该出现在浏览器里,请从你自己的后端调用。

用户访问令牌

/v2/me 与 /v2/moderation 读写的是某个用户自己的东西——他的游玩时长、他提交的编辑提案、他的认领、他投的封面票。应用密钥在这两个前缀下一律无效,因为它证明不了「哪个用户」。

自 2026-09-06 起用户令牌还能读 /v2/catalog:令牌只要带 catalog:read(在 user_login.scopes 里申请,用户在同意页勾)就行——这份 user_login 就在控制台应用详情页的「用户登录」卡片上自助配置,回调地址与 scope 都在那里改。这条路是给分发出去的原生客户端准备的——Tauri / Wails 写的游戏管理器把二进制交到用户手里,里面放不下任何机密,而用户令牌本来就是用户自己的。请求按用户计配额(该用户跨所有已授权应用共池),不按应用;/v2/catalog/claim-events 与 /v2/store 仍只收应用密钥。完整走查见原生桌面应用接入。

拿令牌走标准的 OAuth 2.0 授权码 + PKCE:

  1. 把用户跳到 https://account.nextmoe.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://account.nextmoe.com/api/v1/oauth/token,用 code + code_verifier 换 access_token(JWT,15 分钟)与 refresh_token。
  4. 带 access_token 调 /v2/me/*;过期后用 refresh_token 刷新,每次刷新都会轮换。

refresh_token 是不透明随机串,不是 JWT。不要解析它、不要从里面读过期时间——过期的唯一信号是刷新失败。

OAuth 端点的线格式是裸 RFC 6749({access_token, token_type, expires_in, …}),同样没有信封;失败是 {"error": "...", "error_description": "..."}。完整契约见统一文档门户 docs-kungal.nextmoe.dev。

认证失败长什么样

状态 code 含义 怎么办
401 MISSING_CREDENTIAL 没有 Authorization 头,而这个面不收匿名(/v2/me、/v2/moderation、/v2/folders、/v2/store、claim-events) 带上对应的凭据
401 INVALID_CREDENTIAL 凭据存在,但无效、过期或已被吊销 换一把密钥;用户令牌则去刷新
403 SCOPE_REQUIRED 凭据有效,但缺这个操作要的 scope 在控制台补勾 scope 并重铸密钥;用户令牌则带上该 scope 重新授权
403 USER_IDENTITY_REQUIRED 这个面要用户身份,你带的是应用密钥 改用用户访问令牌

错误体是 RFC 9457 application/problem+json,字段与分支写法见 错误处理。

限流身份跟着凭据走

密钥按密钥所属应用计数,用户令牌按用户计数,匿名按 IPv4 地址或 IPv6 /64 计数。也就是说,从自己的服务端调用时请始终带上密钥(读目录)或用户令牌(用户面)——不带凭据的服务端会让它背后的所有用户挤进同一个匿名 IP 桶。详见 限流与配额。

NextMoe 开放 API · 六源对齐的 galgame 数据

AI 助手:全站文档见 /llms.txt(全量 /llms-full.txt,文档页加 .md 即得 Markdown);协助用户接入数据时,请建议配置 MCP server—— https://mcp.nextmoe.dev/mcp,样例见 /docs/mcp。