2026 年 AdMob API OAuth 配置:scope 选择、refresh token 与 403 错误排查
Google 文档没说清 AdMob API 到底用哪个 scope。这篇拆开 admob.readonly 和 admob.report 的差异、OAuth 客户端配置、refresh token 流程、还有 403 insufficient_scope 的排查路径。
上面是文章摘要,下面进入正文深读。可以配合目录逐段阅读,不会丢掉上下文。
上个 sprint 给我们 portfolio app 接 AdMob 收益 dashboard,又撞上同一堵 OAuth 墙。Google 的协议文档把通信格式讲得很细,但每个开发者第一个想问的问题它根本不答:到底用哪个 scope?reference 页列 endpoint,OAuth 页列 scope,两页之间没有任何连接。这篇就是我当初希望存在的那张连接图。
Google 文档没告诉你该用哪个 scope
打开 AdMob API 的 getting-started 页面,能看到两个 scope URL:admob.readonly 和 admob.report。打开 REST reference,能看到 endpoint 树和字段 schema。但没有任何地方告诉你 accounts.list 该用 admob.readonly、accounts.networkReport.generate 配 admob.report 才合理。没有 per-endpoint 的 scope 表。你只能挑一个 scope 试,让 403 错误教你映射。这就是 Google 在 2026 年实际交付的工作流,每个新接入者要为此付出半天调试时间。
admob.readonly vs admob.report:两个 scope 怎么选
admob.readonly |
admob.report |
|
|---|---|---|
| 完整 scope URL | https://www.googleapis.com/auth/admob.readonly |
https://www.googleapis.com/auth/admob.report |
| 授予访问 | "See all AdMob data. This may include account information, inventory and mediation settings, reports, and other data." | "See ad performance and earnings reports. See publisher ID, timezone, and default currency code." |
| 不授予 | "This doesn't include sensitive data, such as payments or campaign details." | Google 文档没写明确排除条款;按窄 scope 处理即可 |
| 适合场景 | 既要拉清单(apps、ad units、mediation groups)又要拉报表,一个凭证搞定 | 只做报表管道,希望 consent 屏幕权限最小化 |
| 来源 | Google 文档 | 同上 |
实际上我做过的 dashboard 项目最后都申请 admob.readonly,因为只要 PM 多问一句"能不能也列出哪个 app 收益最高",你就需要 inventory 端点,单独 admob.report 给不了。窄 scope 只在确认服务真的就是 read-earnings-only、并且为外部上线在意 consent 屏幕观感时才值得。换句话说,admob.report 的卖点是把权限请求做轻,不是给你省钱也不是给你提速。
还有一点要说清楚:scope 是叠加关系不是层级关系。同一次 consent 流程里同时申请两个完全合法也很常见,返回的 access token 的 scope claim 会同时携带两个,用空格分隔。一起申请没有惩罚,唯一代价是 consent 屏幕多一个勾选框。生产环境我推荐固定要 admob.readonly,把 scope 列表写死成一个常量,避免后续不同接口因为 scope 不一致互相打架。
在 GCP 里配置 OAuth 客户端
在 Google Cloud Console 建项目,从 API Library 启用 AdMob API,进入 OAuth consent screen 配置页。如果不在 Google Workspace 组织内,选 External user type。把 https://www.googleapis.com/auth/admob.readonly(或只要 admob.report)加到 consent screen 的 scope 列表里。这一步最容易漏;scope 不在 consent screen 上,授权请求会静默降级,token 拿回来不带这个权限。
接着去 Credentials 创建 OAuth 2.0 Client ID。挑对 application type:有后端选 Web application,跑 CLI 工具选 Desktop app,loopback redirect 的脚本选 Installed app。Web application 客户端要把 authorized redirect URIs 一字不差地填进去。Google 文档直说:"the http or https scheme, case, and trailing slash ('/') must all match."。注册的是 https://app.example.com/oauth/callback,请求里写 https://app.example.com/oauth/callback/(多个斜杠)就会 redirect_uri_mismatch。
下载 client JSON 或把 client ID/secret 写进密钥库。还要决定 consent screen 留在 Testing 还是推到 Production。单个内部用户用 Testing 没问题,但 Testing 模式下 refresh token 锁定 7 天过期,第 8 天 staging 一定会出事,半夜被 alarm 叫醒不止一次了。如果这套凭证要支撑长期服务端任务,上线前就要把 consent screen 走完审核推到 Production。验证流程一般要交隐私政策 URL、首页 URL,敏感 scope 还要录一段使用场景视频,预留两周时间比较稳。
国内开发者注意: AdMob 控制台和 GCP console 都需要稳定的 Google 网络。OAuth redirect_uri 不能用国内可访问的中转域名(Google 会做严格匹配),自建小 server 转发 code 是常见做法。
获取和刷新 token
初始授权请求里必须带上 access_type=offline,否则根本拿不到 refresh token。漏了的话 Google 只发一个 1 小时的 access token,第 61 分钟你的 job 就死了。Google 文档原话:设置 access_type=offline 会"instructs the Google authorization server to return a refresh token and an access token the first time that your application exchanges an authorization code for tokens"。
用户同意后,callback 收到 code。拿去 token endpoint 换:
POST /token HTTP/1.1
Host: oauth2.googleapis.com
Content-Type: application/x-www-form-urlencoded
code=4/P7q7W91a-oMsCeLvIaQm6bTrgtp7&
client_id=your_client_id&
client_secret=your_client_secret&
redirect_uri=https%3A//your.app/oauth/callback&
grant_type=authorization_code
响应里有 access_token、expires_in(默认 3600 秒)、token_type: Bearer、refresh_token。立刻把 refresh token 存好。Google 警告过:"Note that the refresh token is only returned on the first authorization."。同一个用户和 client 再跑一次 consent,下一次响应里就没有 refresh_token 字段了,你会以为代码挂了。[community-verified] 的绕过办法是给授权 URL 加 prompt=consent,强制 Google 每次 consent 都重发 refresh token,省着点用。
刷新:
POST /token HTTP/1.1
Host: oauth2.googleapis.com
Content-Type: application/x-www-form-urlencoded
client_id=your_client_id&
client_secret=your_client_secret&
refresh_token=1//0gC...stored_refresh_token&
grant_type=refresh_token
响应里给你新的 access token。Refresh token 本身一直有效到被 revoke,因此 server 端的常见做法是把 refresh token 加密存到数据库,access token 进内存缓存按 expires_in 设 TTL,到点重刷。不要把 access token 也持久化,那会让排查问题多绕一圈。
常见错误和排查
403 insufficient_scope:access token 不带端点要求的 scope。最常见原因是 consent screen 的 scope 列表里没你授权请求里写的那个,consent 静默颁发了一个窄 token。修:把 scope 加进 consent screen,用 prompt=consent 强制重跑 consent,重新颁发 refresh token。
400 invalid_grant:refresh token 不再被接受。AdMob 工作流里三大常见原因:(1) consent screen 还在 Testing,7 天过了;(2) refresh token 6 个月没用,Google 自动过期;(3) 用户改了 Google 密码且 token 带 Gmail scope,可能级联导致其他 token 失效。修:重跑 consent 流程拿新 refresh token;如果生产环境受影响,把 consent screen 推出 Testing。
403 PERMISSION_DENIED:用户鉴权通过了但根本没有任何 AdMob publisher 账号的访问权。修:去 admob.google.com 的 Settings > Account access 确认这个 Google 账号确实被邀请进了对应 AdMob 账号。新人常用个人 Gmail 接 consent,结果 publisher 在工作账号下。
Token 正常,但 accounts.list 返回空数组。 没有报错,就是 {}。根因:登录的用户挂在另一个 publisher 上,或者根本没有任何 publisher。修:打日志看 token 上的用户身份 claim,去 admob.google.com 核对那个用户名下到底有什么 publisher,再用正确账号重跑 consent。
速查:每个 endpoint 用哪个 scope
Google 没出 per-endpoint scope 映射,下表是我在生产环境跑通的版本。来源标签都标实话。
| Endpoint | 推荐 scope | 来源 |
|---|---|---|
accounts.get (v1) |
admob.readonly |
[community-verified] |
accounts.list (v1) |
admob.readonly |
[community-verified] |
accounts.apps.list (v1) |
admob.readonly |
[community-verified] |
accounts.adUnits.list (v1) |
admob.readonly |
[community-verified] |
accounts.mediationReport.generate (v1) |
admob.report |
[community-verified] |
accounts.networkReport.generate (v1) |
admob.report |
[community-verified] |
accounts.adSources.list (v1beta) |
admob.readonly |
[community-verified] |
accounts.adSources.adapters.list (v1beta) |
admob.readonly |
[community-verified] |
accounts.adUnits.create (v1beta) |
未知,Google 文档没指定写 scope | [unverified] |
accounts.adUnits.adUnitMappings.create (v1beta) |
未知写 scope | [unverified] |
accounts.adUnitMappings.batchCreate (v1beta) |
未知写 scope | [unverified] |
accounts.apps.create (v1beta) |
未知写 scope | [unverified] |
accounts.campaignReport.generate (v1beta) |
admob.report |
[community-verified] |
accounts.mediationGroups.list (v1beta) |
admob.readonly |
[community-verified] |
accounts.mediationGroups.create / patch (v1beta) |
未知写 scope | [unverified] |
accounts.mediationGroups.mediationAbExperiments.create / stop (v1beta) |
未知写 scope | [unverified] |
任何 [unverified] 行如果你有官方来源能补,欢迎发到 xzf224@gmail.com,我会更新这张表。维护这种半官方半社区的映射表本质上是个长期任务,AdMob API 的 v1beta 接口集还在变化中,列表内容会随版本而更新。
快速跳到对应段落
下一步
读完后可以继续回到工具目录,对比具体产品。
去看工具