AfterPack Cloud API 参考

api.afterpack.dev 的通信契约:认证、批量混淆端点、限制、速率限制、构建时间预算,以及客户端必须处理的诊断代码。

只要配置了 Pro 密钥,@afterpack/core 和每个框架插件就已经在使用这套协议:认证、multipart 流式传输、重试,全部内置。当你要接入自定义的 CI 集成、在官方包之外编写客户端,或者调试一个你的工具没有预料到的响应时,再来读这一页。

基础 URL 与版本

基础 URL:https://api.afterpack.dev/v1/

API 通过 URL 区分版本,v1 是唯一的版本。在同一版本内,响应字段只会新增,绝不会被删除或挪作他用;破坏性变更会以 v2 的形式与之并存发布。GET /v1/version 返回已部署的版本和环境,GET / 是无需认证的存活检查。

客户端兼容性

两个可选的请求头,缺失或格式错误时都会被忽略,绝不会返回 400:无论哪种情况,curl 脚本或早于这些请求头的客户端都不受影响:

请求头携带内容
X-AfterPack-Client-Version对基于 @afterpack/core 构建的客户端,为已安装的 @afterpack/core npm 版本。
X-AfterPack-Client发起调用的包及其版本,例如 afterpack/0.1.0 或 @afterpack/vite/0.1.0。

比较只在服务端进行,基于 MAJOR.MINOR.PATCH,忽略任何预发布后缀(0.1.0-rc.3 按 0.1.0 处理);服务端无法解析的版本,或者根本没有该请求头,总是会被接受。只要配置了最低版本,每个响应都会携带 X-AfterPack-Min-Client-Version,这样脚本无需触发拒绝就能检查自身状态。检查紧跟在密钥认证之后、读取请求体之前进行:

  • 低于配置的最低版本:426 DIAG_CLIENT_UPGRADE_REQUIRED,附带 details.minVersion。
  • 处于停用窗口期间:410 DIAG_API_SUNSET。

两者默认都未配置。只有当 AfterPack 为这套 API 设置了最低版本或停用日期后,工作区才会遇到它们。

响应还可能在成功的 batch-summary、任何错误响应体以及 Free 层级遥测的 202 上携带一个顶层 notices 数组:{severity, code, message, url?},最多 5 条,每条 message 最长 500 个字符。未知的 severity 按警告处理;除了把它展示出来,不需要做任何处理。

认证

两种凭据,彼此不能互换。

凭据请求头可访问的范围
Pro API 密钥Authorization: Bearer ap_v1_<...>POST /v1/obfuscate/batch 和 GET /v1/usage。密钥自行推导出所属工作区,因此无需发送工作区 id。
控制台会话你登录后浏览器持有的会话一切管理账户的操作:创建、列出和撤销密钥,套餐、账单、成员、项目。需要显式的 workspaceId 和一个工作区角色。

密钥是构建凭据。它只能混淆并报告自己的用量,不能创建另一个密钥、读取账单或更改工作区。这种隔离正是目的所在:放在 CI 里的密钥能消耗你的额度,除此之外什么也做不了。

密钥的形式是 ap_v1_ 后跟 40 个 base62 字符。密钥只在创建时显示一次。服务端只存储一个加盐的 HMAC-SHA256 哈希,因此原始密钥事后永远无法找回,只能撤销。在控制台中创建密钥。

Pro cloud batch-obfuscation request flowYour buildplugin / CLIPOST /v1/obfuscate/batchAuthorization: Bearer ap_…AfterPack cloudobfuscates each fileobfuscation is in-memoryper-file resultsBatch summarycounts + timingYour build outputprotected & written backFiles stream back one by one; the source sent for obfuscation is processed in memory, not stored.

端点

POST /v1/obfuscate/batch

唯一的混淆端点。一次调用就是一次构建。每个文件处理完成后,结果就会以流的形式返回。

请求:multipart/form-data。

部分必需说明
batch是批次清单,作为一个 JSON 字符串部分。
每个文件一个部分是JS 源码,位于其清单条目声明的 partName 下。
每个 source map 一个部分可选上游 source map,位于该条目的 sourceMapPartName 下。

清单格式如下:

{
  "version": "1",
  "files": [
    {
      "filePath": "assets/app-a1b2c3.js",  // echoed back on the result part
      "partName": "file0",                 // names the form part carrying the source
      "sourceMapPartName": "map0",         // optional
      "config": { "regions": [ /* ... */ ] } // optional, per-file
    }
  ],
  "config": { "preset": "hard", "seed": 42 }, // batch-level EngineConfig
  "git": { "commitSha": "…", "ref": "…" }     // optional build attribution
}

批次级的 config 是一个引擎配置对象:配置页面上的每一个键都可以使用,包括 preset、seed 和 regions。filePath 和 git 是清单级字段;参见上面的清单结构。

filePath 不能包含引号或 CR/LF 字符。它会被原样回写到结果部分的 Content-Disposition 中,格式错误的路径会被拒绝。

curl https://api.afterpack.dev/v1/obfuscate/batch \
  -H "Authorization: Bearer ap_v1_xxx" \
  -F 'batch={"version":"1","files":[{"filePath":"dist/app.js","partName":"file0"}],"config":{"preset":"hard"}}' \
  -F file0=@dist/app.js

响应:200,响应体是流式的 multipart/mixed。各部分随文件处理完成而陆续到达,所以每收到一个就写入磁盘,而不是把整个批次缓存在内存里:

  1. 每个文件一个 obfuscation-result 部分:{ version, filePath, status, output, outputMap, processingManifest }。失败时 output 和 outputMap 为 null,processingManifest.diagnostics 说明原因。
  2. 最后一个 batch-summary 部分:{ version, totalFiles, successCount, failureCount, engineVersion, results, totalDurationMs, totalInputBytes, totalOutputBytes }。

单个文件失败不会中止整个批次。其他每个文件照常以流的形式返回,汇总会报告成功与失败的分布。

GET /v1/usage

工作区用量和额度。这是密钥唯一能做的一类管理性质的读取,让 CI 任务可以检查自己剩余的额度。既接受 Pro API 密钥(自行推导出所属工作区),也接受控制台会话加上 ?workspaceId=。

{
  "tier": "indie",
  "workspace": { "id": "…", "name": "…" },
  "allowance": { "remainingMb": 412.5, "totalMb": 500, "resetAt": "…" },
  "credits": { "balanceMb": 20 },
  "subscription": { "status": "active", "plan": "indie_monthly", "renewsAt": "…", "graceUntil": null, "lapsed": false },
  "recentBuilds": [
    {
      "buildId": "…", "builtAt": "…", "fileCount": 61,
      "totalBytes": 3145728, "outputBytes": 9437184,
      "meteredMb": 3.0, "allowanceMb": 3.0, "creditMb": 0, "overageMb": 0,
      "projectId": "…", "cloudMs": 39120
    }
  ]
}

outputBytes 和 cloudMs 未知时为 null,绝不会用 0 代替。allowanceMb / creditMb / overageMb 是 meteredMb 在三个池之间的拆分。

限制

限制取值
单个文件50 MB
每批文件数200
批次总字节数500 MB

超出其中任何一项,请求都会在计量任何内容之前被整体拒绝,返回 413 以及 DIAG_FILE_TOO_LARGE 或 DIAG_BATCH_TOO_LARGE。从控制台一侧看到的同样数字,见 API 密钥。

速率限制

在一个滚动的 60 秒窗口内,按密钥和按客户端 IP 分别计数:

范围限制
按密钥,免费工作区10 次请求 / 分钟
按密钥,付费工作区100 次请求 / 分钟
按客户端 IP100 次请求 / 分钟

超出任一范围都会返回 429 和 DIAG_RATE_LIMITED,以及一个 Retry-After 请求头,其值为触发限制的窗口实际清空前剩余的时间。如果速率限制检查本身无法运行,请求会失败即中止,返回 503 DIAG_RATE_CHECK_UNAVAILABLE 和 Retry-After: 5。

构建时间预算

整个 bundle 在一次调用内完成混淆,受 120 秒 CPU 限制约束。两个挂钟时间预算让构建保持在这一限制之内。

已用时间发生什么
45 sProtection Map 采集停止。混淆继续进行直到完成;存储的地图被标记为不完整,跳过原因为 deadline。
90 s不再开始处理新的文件。剩余的每个文件都会以流的形式返回一个明确的失败,带有 DIAG_BUILD_DEADLINE,并注明 N 个文件中完成了多少个,随后流正常关闭。

如果你触及了第二个预算,就拆分批次。

诊断代码

错误除了 HTTP 状态外,还携带一个稳定的 code,与引擎自身的诊断同属 DIAG_* 命名空间。客户端必须处理的代码如下:

代码状态含义
DIAG_INVALID_KEY401密钥缺失或无法识别。
DIAG_KEY_REVOKED401密钥已被撤销。
DIAG_KEY_EXPIRED401密钥已超过其 expiresAt。
DIAG_KEY_OUT_OF_SCOPE403密钥有效,但从其 allowedIPs 列表之外的 IP 使用。
DIAG_CLIENT_UPGRADE_REQUIRED426客户端低于配置的最低版本。携带 details.minVersion。参见客户端兼容性。
DIAG_API_SUNSET410此 API 版本已停用。参见客户端兼容性。
DIAG_ENTITLEMENT_LAPSED402订阅付款逾期。携带 graceUntil 和 upgradeUrl。
DIAG_QUOTA_EXHAUSTED402配额 + Extra MB(如已启用,再加上超额上限)不足以支付这次构建。携带 remainingMb、requestedMb,以及一个区分“配额用尽”和“达到超额上限”的 reason。
DIAG_RATE_LIMITED429见上文速率限制。携带 retryAfterSeconds。
DIAG_RATE_CHECK_UNAVAILABLE503速率限制检查失败;失败即中止。请重试。
DIAG_INVALID_MANIFEST400multipart 请求体格式错误、缺少 batch 部分,或清单未通过校验。
DIAG_FILE_TOO_LARGE413某个文件超过 50 MB。
DIAG_BATCH_TOO_LARGE413超过 200 个文件或总计 500 MB。
DIAG_BUILD_DEADLINE不适用按文件出现在 200 流中。构建在开始处理这个文件之前就用完了时间预算。
DIAG_ENGINE_FAILURE不适用按文件出现在 200 流中。引擎在处理这个文件时抛出了异常。

最后两项不是 HTTP 状态。它们以 status: "failure" 的形式出现在一个成功响应内的 obfuscation-result 部分中。只检查 HTTP 状态的客户端,会把部分失败的构建误认为是完全成功的构建。

重试与退避

状态可重试行为
400、401、402、403、413否修正请求。
429一次严格遵循 Retry-After,上限 120 s;第二次 429 即失败即中止。
500否引擎 bug;请报告它。
503、504是1 s、4 s、16 s(三次重试,共四次尝试),然后失败即中止。

官方客户端还会对整个请求施加 120 秒的超时,并把 DNS、TLS 和连接重置失败视为云端不可达,而不是无限重试。

从客户端角度看密钥生命周期

创建、限定范围和撤销都是控制台操作。对调用这套 API 的代码来说,有两种行为很重要:

  • **构建进行中被撤销。**在批次流式传输期间被撤销的密钥,只会在下一次请求时返回 401;已经在处理中的请求会正常完成。
  • 限定了 IP 列表的密钥在其他任何地方使用时返回 403 DIAG_KEY_OUT_OF_SCOPE,这与无效或已撤销密钥得到的 401 不同,因此客户端能区分“密钥不对”和“密钥对了,但地方不对”。
计量是原子的,并且最先执行

放不进工作区各个池的批次,会在分发任何文件之前被整体拒绝,各个池保持不变:绝不会部分处理,也绝不会部分计费。这是失败即中止规则在批次层面的体现。

源代码处理

发送来混淆的源代码仅在内存中处理,仅持续那一次请求的时长,从不写入磁盘、日志或搜索索引。日志只记录请求 id、文件数、字节总数和诊断代码,从不记录文件内容、标识符名称或常量值。

有一项产物会比请求存在得更久:为控制台按构建存储的 Protection Map。它包含你的原始源代码,并按每个词元经历了多少变换着色。它对每个项目默认开启,只有所属工作区的成员可以读取,保留一年且每个项目最近 500 次构建(以先到者为准),并且可以在项目设置中关闭。完整细节见隐私与数据处理。

下一步

  • 部署模式:这套协议相对于本地 Free 构建处于什么位置。
  • Pro:Pro 在本地引擎之上增加了什么。
  • 隐私与数据处理:存储了什么、存储多久,以及谁可以读取。
  • 套餐:MB 额度、超额,以及每个套餐针对这套 API 解锁了什么。
  • API 密钥:创建、限定范围、轮换和撤销这套 API 所认证的凭据。
  • CLI 参考:实现了这些规则的官方客户端。
  • 配置:config 字段所携带的引擎配置。