做了一个 iOS 原生 BYOK Agent App。

最初叫 iClaw,后来改名成了 Palvia。

功能大概是多 Agent、记忆、技能、端侧 JS Sandbox、浏览器工具,以及一点 Apple 健康数据相关能力。用户自己填 API Key,App 不卖模型额度,也不打算冒充任何模型厂商的官方客户端。

最开始我以为,做这种 App 最大的风险会是:

  • JS Runtime 执行代码被毙掉;
  • LLM 幻觉太多无法正常执行任务;
  • RAG 没有好落地的方案。

后来发现,我还是把世界想得太工程化了。

真正困难的不是把 App 做出来。真正困难的是,把 App 做成一个能被 Apple 审核老师在某个未知设备、未知网络、未知时间、未知心情下理解的东西。

七次提交,六次没有通过,前后折腾三个多月。

这三个月里,最稳定的不是审核进度,是我每周收到的那句“感谢您的耐心等待”。

对于大公司来说,三个月可能就是一个季度的 OKR。对于一个 AI 小 App 来说,三个月可能意味着:

  • 一个模型从领先变成普通;
  • 一个热点从“新机会”变成“已经被做烂”;
  • 一批竞品从 Demo 到融资再到上架;
  • 用户从“哇,这很新鲜”变成“哦,我已经装了三个类似的”;
  • 一个原本还能成立的商业模式,被系统级入口、模型厂商、超级 App 或更快的 clone 慢慢吞掉。

所以这篇不是严格意义上的审核经验分享。更像是一款小 App 在 App Store 审核系统里漂流三个月后留下的航海日志。


时间线:一款 App 如何被拆成七个版本的人格

次数结果审核意见我的理解
第一次拒审启动时意外退出审核老师在另一个宇宙里看到了 Crash
第二次拒审iPhone、国区模型品牌、HealthKit 场景副标题、截图、地区文案、权限说明都能各自成为人生支线
第三次主动撤回已进 In Review,卡一个月,审核 Key 过期Apple 说已经加急,但“加急”是精神层面的
第四次拒审iClaw 可能与 OpenClaw 有关联名字像另一个名字,于是整套身份重做
第五次拒审HealthKit 在 App 内不明显;“未提供 API Key”文档不算,Review Notes 可能也不算
第六次拒审Keywords 里有 objectionable content / services 相关内容改名之后,关键词里的旧世界也要一起埋葬
第七次通过Apple 审核宇宙暂时允许该 App 存在

第一次:审核说它崩了,但没有任何证据证明它在这个宇宙崩过

2.1.0 — Performance: App Completeness

审核原话大概是:

Your app closed unexpectedly when we launched it.

翻译一下:你这个 App 打开就没了。

这次我不敢说完全冤。

TestFlight 上确实有极少数用户反馈启动 Crash,概率大概 1%。理论上,审核老师刚好撞上这条支线,也不是不可能。

问题是,审核会给出测试平台信息。于是我第一时间去查 TestFlight。

结果很精彩:

  • 对应审核平台,没有 Crash 记录;
  • 同平台模拟器跑,正常;
  • 冷启动、首装、常规路径,全都正常;
  • 没有异常日志,没有堆栈,追问也没有;
  • 没有告诉我卡在哪个页面;
  • 唯一能确认“它崩过”的证据,是审核老师的一句话。

这很像在公司里收到一张 P0 工单:

线上挂了。没给机器名。没给时间戳。没给 trace。没给日志。没给复现路径。但请尽快修复。

严格来说,我无法证明审核说错了。也许是某个首装状态,也许是审核网络,也许是 Keychain、本地数据库、权限初始化、时间同步、迁移路径里的某个隐藏分支。

但作为开发者能拿到的信息,基本只有:它崩了,你自己想办法。

于是只能把所有首启路径重新加固一遍——首装、冷启动、无网、弱网、空数据、权限拒绝、Keychain、本地数据库、旧版本升级、API Key 缺失,全排一遍。不是因为我知道哪里有问题,而是因为 Apple 说有。

这大概是 App Review 的第一课:

审核反馈不是 Bug Report,它更像一句预言。你无法验证它,但最好按它活下来。


第二次:语言的禁忌比想象的多

第二次审核一次性来了三条。我开始意识到,Apple 不是在审核一个 App,它在审核你整个人类表达系统。

你写的标题、副标题、长描述、更新说明、关键词、截图里的字、截图里的模型名、App 内说明、支持页面,以及你以为没人会认真看的地方——都有可能在未来某个下午,被转化成一条拒审理由。

1. 副标题里不能写 iPhone

5.2.5 — Legal: Intellectual Property

我在副标题里写了 iPhone,审核说不行。

逻辑上当然能理解,iPhone 是 Apple 商标,不是可以随手塞进文案的普通名词。但作为一个 iOS 开发者,体验大概是:

我做了一个跑在 iPhone 上的 App。我说它和 iPhone 有关。Apple 说:请不要使用 iPhone。

于是删掉。这件事没什么可争的,只是它提醒了我——在 Apple 的世界里,iPhone 不是语言的一部分,而是一种需要正确摆放、正确引用、最好尽量少碰的法务元素。以后看到 Apple 产品词,我都会先想一句:这四个字,值不值得再发一个 build?

2. 中国区不能提 OpenAI / Anthropic,于是我给同一个 App 换了一套模型宇宙

5 — Legal

第二条是,国区的 metadata、文案和截图里,不能提 OpenAI、Anthropic 及其产品。

这不是简单删几个词的事。因为 App Store 上的“文字”并不只存在于 Description,审核看的还包括副标题、长描述、更新说明、截图文字、截图里的模型列表、关键词、支持页,以及你以为只是“产品展示”的那部分现实。

所以最后做的不是把品牌名打码,也不是全改成“支持某些模型服务”“支持兼容接口”这种企业采购文档语言,而是把国区整套模型叙事换了一遍:原本提到 OpenAI / Anthropic 的内容,统一换成 GLM、DeepSeek、Qwen。

于是同一个 App,在不同地区拥有了不同的 AI 宇宙。海外用户看到一套模型名单,国区用户看到另一套。底层还是同一个 App,Agent 还是那个 Agent,设置页还是那个设置页,用户还是得自己填 Key——只是 App Store 截图里服务人类的模型,换了一批。

从产品本地化角度说,这其实不算不合理,GLM、DeepSeek、Qwen 本来就是国区用户会实际配置的服务。但从开发体验上说,还是有种互联网地缘平行宇宙的味道:

你以为你在上传截图,其实你在为不同地区制作不同版本的现实。你以为你在做本地化,其实你在维护多套可被审核接受的世界观。

3. HealthKit / CareKit 的用途需要说明

2.5.1 — Performance: Software Requirements

第三条是 HealthKit / CareKit 的使用场景不够明确。这条本身很合理,我补充说明:用户主动授权后才读取数据;读取例如步数、睡眠等健康数据;用于让 Agent 提供更贴近日常状态的建议;用户可以不授权、可以随时关闭;不用于广告,不用于无关用途。

我以为这已经是标准答案。后来第五次审核告诉我:你写在文档里,不算;你要在 App 里把它做成一个审核老师两步之内能撞到的东西。

这很像向物业申请装修。你交了图纸,物业说:图纸不算,你先把房子装出来,我看看你是不是真的在装修。


第三次:Apple 告诉我已经加急,然后让我在 In Review 里领悟“加急”的哲学

第三次提交后,App 很快进入了 In Review。

这是一个很容易让开发者产生错觉的状态。看到 In Review,人会自然脑补:审核老师已经打开 App,正在点首页,正在看权限,正在读 Review Notes,正在复制 API Key,或者至少已经把这个 build 放进今天要处理的列表。

后来我才知道,In Review 更像一种哲学状态。它表示:你的 App 已经不在队列外面了。至于是否真的有人在 review、review 到哪、审核老师是否还记得它,不属于开发者可观察的范围。它就在 In Review 里安静地待着。

一开始我发邮件问进度。第一次回复里,Apple 还主动告诉我一个好消息:

这个 App 已经被给予加急审核。

不是我申请的,是 Apple 主动说“我们已经注意到你,并且已经加急”。看到这句时,我确实短暂感受到了现代平台服务业的温度——“加急审核”听起来像一个明确承诺:好的,我们知道你等得久,接下来会优先处理,事情应该会快一点。

然后,它继续停在 In Review 里。

一个月。

加急。In Review。一个月。这几个词放在一起,有一种非常 Apple 的抽象美。

我后来理解,“加急”的含义可能不是“更快完成”,而是:平台已经在精神上更关心你了。

之后我每周发一封邮件,问题都尽量问得具体:当前有没有实质进展;是否缺少测试资料;是否需要更新审核 API Key;审核人员是否成功进入需要 Key 的路径;有没有任何我可以补充、修复或说明的问题。

Apple 的回复也始终很稳定。正式语气当然非常礼貌,大意是:审核仍在进行,请耐心等待,完成后会通知。但连续读几周之后,精神内核大概就是——never gonna give you up

我问:现在卡在哪里?
Apple:不要放弃。
我问:API Key 是否已经成功使用?
Apple:不要放弃。
我问:加急审核大概还要多久?
Apple:不要放弃。

于是我们形成了一种非常稳定的客户关系:我持续提供焦虑,Apple 持续提供鼓励。唯一没有持续提供的,是审核进展。

那段时间,我还专门去 Apple Developer Forums 搜类似经历。一开始只是想确认一件事:到底是我这个 App 有什么特殊问题,还是 Apple 审核偶尔会把人忘在某个状态里?

然后我看到了一个很熟悉的世界。有人卡在 Waiting for Review 半个月;有人进了 In Review,然后一个月没有任何变化;有人说审核账号、测试服务器、测试订阅都快过期了;有人怀疑审核人员根本没打开过 App;有人发了一封封邮件、预约了 App 审核的线上会议,状态还是一动不动。

帖子下面通常有两类回复。第一类是 Apple 的模板:您的问题已经反馈给了 APP 审核,他们会在 App Store Connect 后台联系你。第二类是开发者的跟帖,一开始还比较克制——“我也是,还在等”“已经十天了”“有没有人最近通过了”“我也收到同样回复”——再往下,语气会慢慢变化:

第三周了,还是 In Review。
测试账号快失效了。
我已经取消重新提交了。
重新提交后又回到 Waiting for Review。
压根没有收到任何反馈。
我放弃了。

Apple Developer Forums 里最稳定的社区共识,不是某个 API 的最佳实践,而是:大家都在等,大家都不知道在等什么,大家都收到了同一种回复,然后有人某天被轻飘飘拒审了,有人取消重提,有人再也没回来更新。

像候机楼里一群人盯着同一块航班屏幕,状态一直是 Delayed。没有新的登机口,没有预计起飞时间,没有解释。偶尔广播里传来一句:感谢您的耐心等待。

你去论坛,本来是想找一个解决办法,最后找到的是一群和你一起被安慰的人。

最后,事情进入了一个特别符合 AI App 时代的结局:审核仍在 In Review,但为了配合审核准备的 API Key 先过期了。于是我只能主动撤回。

整个链路大概是:提交 → In Review → Apple 邮件告诉我已加急 → 继续 In Review → 每周催一次 → 每周收到一次“请耐心等待” → API Key 过期 → 主动撤回 → 回到起点。

从某种意义上说,Apple 也没骗我。它确实没有放弃我的审核,它只是很长一段时间没有明显地继续做什么。


给 Apple 找个借口:可能真的是 AI 生成 App 把审核队列打爆了

站在 Apple 的角度,也不是完全找不到借口。

最近 AI 生成 App 的门槛确实低得离谱。以前一个人想做 App,至少还得会一点 Xcode、Swift、界面、后端、登录、支付、崩溃处理,最后花几个月把“我有个 idea”磨成“这东西至少能装进手机”。

现在的路径大概是:想到一个 AI idea → 打开 Cursor、Claude Code 或 Codex → 让它生成一个 SwiftUI 界面 → 接几个模型 API → 再加一个订阅页 → 三天后提交 App Store → 标题叫 AI Life Copilot Pro。

从供给侧看,Apple 审核队列大概确实正在经历 AI 时代的洪水。每天可能涌进无数个:套着不同图标的聊天壳、换了不同 prompt 的心理咨询师 / 健身教练 / 恋爱导师 / PDF 总结器,再加一个订阅页,然后一起冲向 App Review。

审核老师上午审一个“AI 睡眠伙伴”,中午审一个“AI 情绪树洞”,下午审一个“AI 商务助理”,晚上再看到一个“多 Agent、记忆、工具调用、用户自带 API Key、能访问健康数据”的 App。他可能也很难第一眼判断:这是一个经过设计的产品,还是又一个 prompt 套壳加订阅页。

所以审核慢,也许不是 Apple 故意摆烂,而是整个系统突然被 AI 时代的低成本供给打爆了。

但这个解释不会让小团队舒服多少。因为大公司面对队列拥堵,可以扩审核员、改流程、加自动化、增加状态透明度、给开发者更明确的 SLA,至少告诉人到底卡在哪。小开发者面对队列拥堵,通常只能每周发一封邮件,收到一封“感谢耐心等待”,去论坛看别人是否也在等,顺便观察自己的 API Key、测试账号和 idea 的市场窗口,哪个会先过期。

这是一种很 AI 时代的结构性喜剧:

AI 让做 App 变得更快,也让提交审核的人变得更多,审核因此变慢,小团队于是花更多时间等待。最终最稀缺的不是生成代码的能力,而是进入 App Store 的时间。

Apple 可能确实不是故意拖你,它只是恰好站在 AI App 洪水的闸门前面。而闸门后面排着无数个 AI Copilot Pro——很不幸,你也是其中之一。


第四次:iClaw 不行,因为它看起来像 OpenClaw 的亲戚

1.1.6 — Safety: Objectionable Content

第四次终于有了明确反馈,核心意思是:你的 metadata 可能误导用户,以为 App 和 OpenClaw 有关联。

我原本叫 iClaw。我的理解是:这是一个有早期苹果味道的、像移动端 Agent 的名字。Apple 的理解是:这听起来像 OpenClaw 的 iOS 客户端。

Apple 审核最有力量的一点在于:它不需要证明你真的在冒充,它只需要认为用户可能会误解;而用户到底会不会误解,最终由 Apple 代替用户回答。

于是 iClaw 死了。我没有继续解释“我没有官方关系、不是客户端、没用对方 logo、只是用了 claw、不是故意蹭”——因为这种解释在审核语境里基本没有意义。只要“看起来像”成立,你就进入整改分支。

最后直接改名:iClaw → Palvia

跟着一个名字一起重做的,包括 App 名、图标、App 内文案、官网、支持页面、截图、宣传材料、metadata,以及——工信部备案。

对,重新走备案。一个产品名和另一个产品名发生了语义联想,最后我多走了一遍行政流程。互联网的蝴蝶扇一下翅膀,开发者去重新备案。

题外话是,重新备案居然比重新过 Apple 审核轻松。

App 备案这种流程平时经常被吐槽,大家默认它应该意味着:填表、上传材料、等待、补材料、电话确认、再等待,以及某个不知道谁维护的网站在最后一步报错。

结果实际体验里,基本一天以内就批了。提交、等待、通过。没有让我解释 Agent 是什么,没有让我证明为什么叫 Palvia,没有让我提供 API Key,没有问我和 OpenClaw 是否存在精神层面的亲缘关系,没有说系统曾意外退出但不给日志,没有让我每周写邮件确认自己还活着。它甚至比 Apple 把 build 从 Waiting for Review 推进到 In Review 更快。

这让我产生了一种不太礼貌但很真实的感受:一个经常被视为额外行政负担的流程,反而是整条发布链路里最像现代服务业的一环——至少它有输入、有处理、有输出。

而第四次改名重提后,App 在 Waiting for Review 里卡了半个月,这次连 In Review 的哲学状态都还没进入。如果第三次像文件已经放到审核老师桌上、但没人翻页,那第四次更像文件还在前台排队,前台不断告诉你:系统正在处理中。

后来几次终于恢复到比较“正常”的节奏,差不多一周等来一个结果。只是那个结果通常不是通过,而是新的拒审理由。


第五次:文档里的 HealthKit 不算,API Key 明明给了也不算

第五次是整个流程里最有 Apple Review 精神的一次:一半完全合理,一半像系统在和我玩猜谜。

HealthKit 的场景在 App 内不够显著

审核说,你虽然在文档里解释了 HealthKit 用途,但 App 内展示得不够明显。

于是我立刻补了一个 Landing 流程,加上 Apple 健康能力介绍、授权用途解释、健康功能入口、设置里的 Apple 健康菜单、权限状态、更明确的产品路径。简单说,就是把原本“功能上存在”的 HealthKit,做成一个审核老师一进门就能撞见的 HealthKit。

这一条后来我觉得挺对。审核不会替你推理,用户也不会替你推理。你不能期待别人从“申请了权限 + 文档里提了一句”推导出“这里应该有一个完整的健康场景”。你得把门做出来,门牌上写 Apple 健康,门后面放功能,门口再贴一张纸:我们真的会用这个权限。

审核说:你没有提供 API Key

然后审核说:没有提供 API Key,无法体验完整功能。

问题是,我提供了,就在 Review Notes 里。我检查过:Key 有效、有额度、没过期、调用正常、填写位置正确、说明也写了,我甚至找了一台新的 iPad 重新按审核路径自己走了一遍。

但审核说没有。

此时一个开发者最自然的反应是:你再看看?但你不能真这样写。因为你不是在和一个会回复 Slack 的同事协作,你是在向一个具有最终裁决权、却几乎不暴露过程的系统提交补充材料。

于是我重写了一份更详细的 Review Notes,详细到接近幼儿园操作手册:打开 App → 点这里 → 点那里 → API Key 填在这里 → 复制下面这段 → 选这个服务 → 选这个模型 → 输入这句测试问题 → 如果没成功,请先检查前八步。

更微妙的是,从 API Key 的使用记录来看,我甚至怀疑最终通过时,审核老师可能根本没真正深入到必须使用 API Key 的完整路径。也就是说:先因为“没提供 API Key”拒了,我重写了说明,最终通过时,对方可能压根没用过 API Key。

这不是说审核老师故意怎样。只是我开始明白:

审核反馈不等于审核过程日志。你收到的是结论,中间发生了什么,不属于你可见的世界。


第六次:改名之后,关键词里的旧世界也要一并消失

1.1 — Safety: Objectionable Content

第六次审核说 metadata 里仍有 terms or images that reference objectionable content or services。

我翻了一遍,最可疑的是 Keywords 里残留的 OpenClaw。

这就很有意思。前面我已经因为 iClaw 和 OpenClaw 的关联改了名,但改名不够,关键词里还留着,也可能不够。

所以这次直接进行一次 metadata 扫墓:清掉 OpenClaw,清掉可能造成第三方关联的词,清掉可能被理解为导流、蹭流量、攀附生态的词,全文搜索名称、副标题、关键词、描述、截图、更新说明、官网和 App 内文案。

从此我对 Keywords 的理解变了:

你以为它是 ASO,Apple 认为它是人格档案。你写进去的每一个词,未来都可能被问一句:你为什么和它有关系?


第七次:终于上架,Apple 审核宇宙短暂允许我存在

第七次提交,终于通过。

看到通过通知时,没有什么史诗级胜利感,更多是一种:好,今天这个版本恰好通过了当前时空的审核状态机。

回头看,Apple 审核当然不是完全没道理,很多问题本身都合理:启动 Crash 应该修;HealthKit 的用途应该说明;不应该让用户误解第三方关系;审核人员应该能体验核心功能;Apple 商标不能随便用;地区内容需要符合地区要求。

问题不在于它有没有规则,而在于小团队需要在一个极其不对称的时间系统里遵守这些规则。

开发者这边,产品窗口按天算,热点按周算,竞品按月算,现金流按季度算,人力按人天算。

审核那边,可能一周,可能半个月,可能一个月,可能已经 In Review,可能已经“加急”;可能给你一个 Crash 但不给日志;可能要求你提供 API Key 但不一定真的使用;可能从 metadata 的某个角落再找到一个词;可能每周回邮件,但每封邮件都只是在提醒你继续等。

对平台来说,这些是正常的审核波动。对小公司来说,这些波动可能就是商业模式的寿命。

尤其在 AI 产品上。今天用户觉得新鲜的能力,三个月后可能已经被系统级入口、模型厂商、超级 App、开源项目、竞品 clone 或一个新模型版本吞掉。很多时候不是你产品做得不够好,而是:你还在等审核,别人已经把这个需求做成默认功能了。

所以后来我会觉得,App Store 审核对小团队最残酷的地方,不是它拒你,而是它可以不需要做错什么,就让你在最关键的时间窗口里慢慢失去速度。

大公司可以把审核当作流程,小公司有时只能把审核当作风险资产。


结尾:审核不是测试,而是一场面向未知观察者的舞台剧

以后如果还做这种依赖外部模型、需要权限、需要审核环境的 App,我会默认:

  • 审核可能遇到一个我无法复现的 Crash;
  • 审核可能看不到 Review Notes;
  • 审核可能看了 Review Notes,但不按说明操作;
  • 审核可能需要 API Key,但不一定真的用;
  • 审核可能一个月都不推进;
  • 审核可能告诉我已经加急,但“加急”的时间单位和人类不同;
  • 审核可能从一个我早就忘掉的 metadata 字段里挖出一个词;
  • 审核可能要求我解释已经解释过的东西;
  • 审核可能不关心我花了多少时间,只关心当前 build 是否恰好落在它认可的表达边界里。

所以产品要尽量做到:首启尽量无依赖;核心路径尽量可见;权限用途尽量显眼;审核环境尽量长寿;外部 API 尽量有降级;Review Notes 尽量写得像面向陌生人的操作手册;metadata 尽量没有历史包袱;名字尽量不像任何热门项目的移动端亲戚。

这不是说 Apple 不该审核,也不是说所有拒审都不合理。只是站在一个小开发者的角度,审核最可怕的不是“严格”,而是:

严格 + 不透明 + 不确定 + 慢。

前者还能靠工程解决,后者会直接吃掉一个 idea 最值钱的东西:时间。

如果互联网产品的竞争是速度,那么审核队列本身,有时就是竞争的一部分——而且是小公司最难买到、最难绕过、也最难解释给投资人和用户听的那一部分。

祝所有正在做自己创意的朋友:

  • 少一次不可复现的 Crash;
  • 少一次 metadata 地雷;
  • 少一次“你没给 API Key”;
  • 少一次改名;
  • 少一次重新备案;
  • 少等一个月;
  • 也少一点在审核队列里,看着自己的 idea 从“值得做”变成“已经有人做完了”的感觉。

最后,贴一下 App Store 链接和 Github 链接,欢迎大家提提意见: