CodexBar 的 Notion AI Provider 接入指南:用量额度窗口、Cookie 鉴权与 Pace 估算
【免费下载链接】CodexBarShow usage stats for OpenAI Codex and Claude Code, without having to login.项目地址: https://gitcode.com/GitHub_Trending/co/CodexBar
Notion AI 从 2026 年 8 月 3 日起开始强制 AI 用量额度(usage allowance),CodexBar 通过内置的 Notion AI Provider 在菜单栏卡片与codexbar usage中呈现Rolling(6 小时滚动)与Monthly(账单周期)两个额度窗口,无需输入账号密码、无需调用任何官方公开 API。读完本文,你将掌握 Notion AI Provider 的启用前提、自动/手动 Cookie 获取方式、config.json工作区固定方法、底层getSpaces与getCreditRateLimitStatus两个内部接口的响应解析,以及 CodexBar 如何用"月度哨兵值"精确计算月度用量的消耗节奏(Pace)。
一、功能概览与适用前提
Notion AI Provider 跟踪 Notion 在Settings → Notion AI → Usage页面展示的两类额度窗口:
- Rolling(主窗口):6 小时滚动窗口,对应接口字段
window; - Monthly(次窗口):账单周期窗口,对应接口字段
billingPeriodWindow。
Notion 于2026 年 8 月 3 日开始强制执行 AI 用量额度。在该日期之前,同一接口会返回"enforcement": "preview",但用量数字本身是真实的,因此 CodexBar 的仪表盘在两种状态下都保持准确。
非官方集成提醒:CodexBar 使用的是 Notion 内部、基于 Cookie 鉴权的
/api/v3端点。这些端点不是受支持的公开 API,可能随时变更或失效。这一点在 NotionProviderDescriptor.swift 中通过 provider 元数据(dashboardURL、statusLinkURL等)与实现细节可以看出,官方并未承诺其稳定性。
额度仅存在于 Business 和 Enterprise 工作区。Free、Plus 和个人工作区会让接口返回{"status":"not_applicable"},CodexBar 会将其作为明确的 provider 错误呈现("Notion AI usage allowance is not tracked for ..."),而不是显示一个空的仪表盘。这一判断逻辑可以在 NotionUsageSnapshot.swift 中看到:NotionWorkspace.mayHaveAllowance只对subscriptionTier为business或enterprise的工作区返回true。
二、启用与配置:自动导入 vs 手动粘贴
自动导入(推荐)
- 在 Chrome 中登录 Notion。
- 在 CodexBar 中打开Settings → Providers,启用Notion AI。
CodexBar 会自动导入浏览器会话 Cookie,并且只将 Cookie 发送到https://app.notion.com。自动导入要求浏览器中必须存在token_v2会话 Cookie——如果浏览器配置里只有 Notion 的其他 Cookie 而没有token_v2,该浏览器配置会被跳过,而不是被用于必然返回 401 的请求。这一点在 NotionUsageFetcher.swift 中有明确注释:"token_v2is the session cookie; without it the API answers 401 for every call."
几个值得注意的实现细节:
- 默认只探测 Chrome,以避免探测无关浏览器存储。使用共享浏览器 Cookie 管道的调用方仍可显式传入浏览器列表(
browserOrder参数)。在 NotionProviderDescriptor.swift 中可以看到,macOS 下默认的browserCookieOrder就是[.chrome]。 - Chrome Cookie 解密可能需要 macOS Keychain 授权;非用户主动触发的刷新(如定时器 tick)不会触发 Chromium 浏览器存储的读取,因此会优先复用上次成功导入缓存的 Cookie 头(见下文"后台刷新机制")。
- 域名去重:同一配置可能同时持有
notion.so与app.notion.com上的同名 Cookie(尤其是遗留的旧token_v2)。deduplicatedByName会按域名优先级保留最具体域名的 Cookie,避免同一个请求头里出现两个token_v2导致服务端任意取值(NotionUsageFetcher.swift)。 - 导入会话有 5 秒 TTL 的内存缓存(
importSessionCacheTTL),避免每次轮询刷新都去读浏览器 Safe Storage。
手动粘贴
在 Notion AI Provider 设置里将Cookie source设为Manual,然后粘贴以下任意一种:
- 裸的
token_v2值; - 从浏览器对
app.notion.com的网络请求中复制出来的Cookie: ...请求头; - 从 Notion Web 应用捕获的完整
curl命令(所有-H参数都会被解析,但只有Cookie头与一组固定的安全请求头会被转发)。
手动捕获 Cookie 的步骤:
- 在浏览器中打开 app.notion.com。
- 打开开发者工具 → Network 标签页。
- 打开Settings → Notion AI → Usage,找到一条
getCreditRateLimitStatus请求。 - 右键 → Copy → Copy as cURL。
- 把完整的
curl命令粘贴到 CodexBar 设置的Notion cookie字段中。
从源码看,curl捕获只会转发forwardedManualHeaders白名单中的请求头(如accept、accept-language、notion-client-version、referer、sec-fetch-*、user-agent、x-notion-active-user-header等),见 NotionUsageFetcher.swift。x-notion-space-id被刻意排除:如果在某个工作区捕获的请求头携带了该字段,而请求体要求的是配置中的另一个工作区,这种不匹配会表现为"显示另一个工作区的用量"而非报错。
工作区选择
属于多个工作区的账号默认选择第一个 Business 或 Enterprise 套餐的工作区。若要固定特定工作区,可在 Provider 设置中设置Workspace ID,或在config.json的notion条目中设置workspaceID。带连字符与不带连字符的 UUID 两种形式都会被接受(normalizeSpaceID会把无连字符的 32 位十六进制字符串规范化为带连字符的虚线形式,NotionUsageSnapshot.swift)。
工作区解析逻辑(resolveWorkspace,NotionUsageSnapshot.swift):
- 若配置了
preferredID且该账号可见,则使用它; - 配置了但账号不可见的工作区 ID 几乎总是笔误——直接查询只会得到模糊的 403,因此回退到自动选择;
- 自动选择规则:第一个
mayHaveAllowance为真的工作区,否则取第一个工作区。
Notion 不支持该 provider 使用独立的环境变量或--cookieCLI 标志,唯一的手动路径就是上述设置字段与config.json。
三、数据来源:两个内部 POST 请求
每次刷新,CodexBar 都会向https://app.notion.com发送两个 POST 请求(NotionUsageFetcher.swift):
/api/v3/getSpaces— 解析当前登录用户(邮箱、姓名)与账号可见的所有工作区,包括每个工作区的plan_type与subscription_tier。这正是自动工作区选择与账号身份行(identity)的数据来源。/api/v3/getCreditRateLimitStatus,请求体为{"spaceId": "<uuid>"}— 返回额度本身。
两个请求都携带默认头(Content-Type: application/json、Origin: https://app.notion.com、浏览器指纹 User-Agent、Referer等),随后叠加手动捕获的白名单头与Cookie头。请求默认超时 15 秒(timeout: TimeInterval = 15)。
getCreditRateLimitStatus的典型响应如下(与文档一致,并被 NotionUsageFetcherTests.swift 以 fixture 方式验证):
{ "status": "within_limit", "window": { "creditType": "basic_ai_credits", "scope": "per_user", "window": "6h", "used": 42.5, "limit": 100 }, "resetsInSeconds": 12600, "billingPeriodWindow": { "creditType": "basic_ai_credits", "scope": "per_user", "cadence": "billing_period", "used": 18.0, "limit": 100, "periodEndMs": 1788000000000 }, "enforcement": "preview" }解析防御性:getSpaces的载荷是以用户 ID 为键的记录映射,解析器会优先挑选"自身notion_user记录能自我识别"的用户键,拒绝歧义响应——绑定错误的键会把另一个账号的额度显示在该账号邮箱名下(resolveUserID,NotionUsageSnapshot.swift)。记录有两种包裹形态(单层{"value": {...}}与双层{"value": {"value": {...}}}),unwrapRecord两者都兼容。parseRateLimitStatus则要求响应中必须出现isNotApplicable或任一用量窗口,否则抛出parseFailed,杜绝把无关的 200 响应体当作 0% 用量上报(NotionUsageSnapshot.swift)。
四、字段映射:滚动窗口、月度窗口与身份
| CodexBar 窗口 | Notion 字段 | 说明 |
|---|---|---|
| Rolling(主) | window.used/window.limit | window.window(6h)决定窗口长度;resetsInSeconds决定重置时间。 |
| Monthly(次) | billingPeriodWindow.used/.limit | periodEndMs决定重置时间,窗口长度即到该重置为止的自然月。 |
| 身份(Identity) | getSpaces | 账号邮箱、工作区名称、首字母大写的订阅等级(如Business)。 |
两个关键设计决策(源码可证):
- 用量百分比是算出来的,不是假设的:
percent(used:limit:)只有在limit > 0时才计算used / limit * 100;缺少或非正的 limit 意味着"无可度量的额度",返回 nil 而非假装成某个百分比,否则原始积分数量级会渲染成离谱的仪表盘(NotionUsageSnapshot.swift)。这保证未来 limit 不是 100 时依然正确。 - 超额值不裁剪:超出额度的用量原样保留,显示层的裁剪(clamping)在下游完成。
resetsInSeconds为 0 是真实答案(窗口正在此刻重置),只有负值才被丢弃(rollingReset)。
不覆盖的部分:Custom Agents 与 Workers 不在该额度覆盖范围内——Notion 用 Notion credits(getAIUsageEligibilityV2)计量它们,本 provider 不读取该接口。
月度哨兵值:Pace 正确性的关键
两条进度条在卡片与codexbar usage中都带有一条期望用量估算,当消耗快于均匀消耗时显示n% in deficit,慢于时显示n% in reserve。
月度估算需要窗口长度,而 Notion 只上报periodEndMs。因此快照携带共享月度哨兵值(ProviderPaceCapability.monthlyWindowSentinelMinutes)作为windowMinutes——正是这个值让 provider 的ProviderPaceCapability匹配;解析时再用"重置点结束的那个真实自然月"替换它,而不是固定 30 天(否则 2 月和任何 31 天的月份都会算错预期用量)。相关实现见 NotionUsageSnapshot.swift 与 NotionProviderDescriptor.swift。
为什么 nil 不安全:UsagePace.weekly会用调用方的defaultWindowMinutes(所有周路径上都是 7 天)替换无长度窗口,而不是跳过它——无长度的账单周期会被按一周来打分;而拒绝无长度窗口的界面则会直接把它从 pacing 中剔除。卡片、菜单栏 pace token、预测性 pace 警告和 CLI 每条路径都会先解析哨兵值,因此它们之间不会出现分歧。
滚动窗口按 session 窗口计算 pace。它的长度来自 API 的6htoken 而不是写死的,因此只有不超过 6 小时的窗口才按此方式计算 pace;更长的窗口属于账单周期,走重置窗口路径。实现上NotionProviderDescriptor.rollingWindowMaxMinutes = 6 * 60,minutes(fromWindowToken:)把6h解析为 360 分钟;rollingMinutes还会把恰好等于月度哨兵值的 token(如30d、720h、43200m)当作不可信长度丢弃,避免把滚动窗口误标成账单周期(NotionUsageSnapshot.swift)。
测试侧,NotionUsageFetcherTests.swift 验证了上述映射:usage.primary?.usedPercent == 42.5、windowMinutes == 360、重置时间等于now + resetsInSeconds,而usage.secondary?.windowMinutes等于月度哨兵值、resetsAt等于periodEndMs,身份行的loginMethod为首字母大写的Business。
五、会话持久化与后台刷新机制
Notion 的会话管理分两层(macOS):
- 内存 Cookie 头缓存(
CookieHeaderCache):上次成功导入的完整 Cookie 头按 provider 缓存。后台定时器 tick 无法读取 Chromium 浏览器存储(cookieImportCandidates在非用户触发刷新时会把 Chromium 浏览器剔除,避免 Keychain 弹窗),所以复用该缓存正是后台刷新继续工作的方式——而不是误报"找不到 Cookie"。 - 磁盘会话存储(
NotionSessionStore):成功导入后只保存token_v2与来源标签到notion-session.json(经CredentialFileWriter.writePrivate私有权限写入,加载时还会repairPermissions),下次后台刷新优先使用它(NotionSessionStore.swift)。
请求顺序与失效处理(NotionUsageFetcher.swift):
- 手动 Cookie 覆盖(Manual 模式)优先;
- 缓存 Cookie 头;
- 磁盘存储的会话;
- 以上都不可用时才做一次全新的浏览器 Cookie 导入。
当缓存的会话被服务端以 401 拒绝时(invalidCredentials),会先清除缓存与磁盘存储,再用一次全新导入重试。相关的错误类型定义在 NotionUsageSnapshot.swift,包括noSessionCookie、cookieImportDeferred、invalidCredentials、noWorkspace、allowanceNotApplicable等,各自的errorDescription与 UI 提示一一对应。
六、CLI 与调试手段
Notion AI 在 CLI 中的名称是notion(别名notion-ai、notionai,见 NotionProviderDescriptor.swift)。在codexbar usage输出中,滚动窗口作为 session 窗口、月度窗口作为账单周期窗口分别显示,并各自带 pace 估算。
config.json中固定工作区的写法(注意 CodexBar 配置文件默认位于~/.config/codexbar/config.json,具体可参考 cli-configuration.md):
{ "providers": { "notion": { "cookieSource": "manual", "manualCookieHeader": "token_v2=xxxxxxxx", "workspaceID": "11111111-2222-3333-4444-555555555555" } } }其中workspaceID是 CodexBar 配置模型中所有支持该字段的 provider 的通用键(CodexBarConfig.swift),Notion 是支持它的 provider 之一,配置校验会核对"设置了workspaceID的 provider 是否确实支持该字段"(CodexBarConfigValidation.swift)。manualCookieHeader支持裸token_v2值、Cookie:头或完整 curl 捕获,NotionUsageFetcher.requestContext会先尝试解析 curl 头字段,取不到时再按裸 token 处理。
调试时,UsageStore的 Notion 调试日志(UsageStore+NotionDebug.swift)会以 15 秒超时运行NotionUsageFetcher.debugRawProbe,输出工作区名、订阅等级、status、enforcement、滚动窗口的window/used/limit、resetsInSeconds以及账单窗口的used/limit/periodEndMs,并附上每次请求的日志行——排查 Cookie 问题与解析问题时非常有用。
七、状态页与故障排查
Notion 发布官方状态页 status.notion.so。CodexBar 会在界面中链接到该页面,但不会轮询其组件。
常见错误与对策:
- "Notion AI usage allowance is not tracked for …"— 所选工作区不在 Business 或 Enterprise 套餐上。在设置中把Workspace ID指向一个符合条件的工作区(对应
allowanceNotApplicable错误)。 - "Notion session cookie is invalid or expired"— 重新登录 Notion,或重新捕获手动 Cookie(对应
invalidCredentials,401 响应触发)。 - "No Notion cookies found"— 浏览器配置中没有 Notion 的
token_v2Cookie。请先登录,或切换为手动 Cookie(对应noSessionCookie)。 - "Notion cookies can only be read during a manual refresh. Refresh CodexBar once to import them."— 浏览器 Cookie 只能在用户主动触发的刷新中读取(避免 Keychain 弹窗),手动刷新一次即可(对应
cookieImportDeferred)。
八、源码索引
如果希望深入阅读实现,以下文件按依赖顺序排列:
- NotionUsageFetcher.swift — Cookie 导入、请求构造、curl 捕获解析、后台缓存与会话回退逻辑;
- NotionUsageSnapshot.swift — 错误类型、工作区解析、响应解码、快照到
UsageSnapshot的映射与 Pace 哨兵值逻辑; - NotionProviderDescriptor.swift — provider 元数据、Pace 能力声明、
NotionWebFetchStrategy取数策略; - NotionProviderSettings.swift — 设置模型(cookieSource / manualCookieHeader / workspaceID);
- NotionSessionStore.swift —
token_v2的磁盘持久化; - NotionProviderImplementation.swift — App 侧设置面板字段与登录跳转;
- UsageStore+NotionDebug.swift — 调试探针入口;
- NotionUsageFetcherTests.swift — 覆盖响应解析、窗口映射、工作区选择与 UUID 规范化的测试;
- NotionSessionStoreTests.swift — 会话持久化测试;
- NotionMenuCardModelTests.swift — 菜单卡片模型测试。
提醒:由于依赖 Notion 内部非公开接口,该 provider 的字段映射与端点行为均以当前仓库实现为准,并可能在 Notion 端变更时随之调整。
【免费下载链接】CodexBarShow usage stats for OpenAI Codex and Claude Code, without having to login.项目地址: https://gitcode.com/GitHub_Trending/co/CodexBar
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考