只要配置了 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 哈希,因此原始密钥事后永远无法找回,只能撤销。在控制台中创建密钥。
端点
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。各部分随文件处理完成而陆续到达,所以每收到一个就写入磁盘,而不是把整个批次缓存在内存里:
- 每个文件一个
obfuscation-result部分:{ version, filePath, status, output, outputMap, processingManifest }。失败时output和outputMap为null,processingManifest.diagnostics说明原因。 - 最后一个
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 次请求 / 分钟 |
| 按客户端 IP | 100 次请求 / 分钟 |
超出任一范围都会返回 429 和 DIAG_RATE_LIMITED,以及一个 Retry-After 请求头,其值为触发限制的窗口实际清空前剩余的时间。如果速率限制检查本身无法运行,请求会失败即中止,返回 503 DIAG_RATE_CHECK_UNAVAILABLE 和 Retry-After: 5。
构建时间预算
整个 bundle 在一次调用内完成混淆,受 120 秒 CPU 限制约束。两个挂钟时间预算让构建保持在这一限制之内。
| 已用时间 | 发生什么 |
|---|---|
| 45 s | Protection Map 采集停止。混淆继续进行直到完成;存储的地图被标记为不完整,跳过原因为 deadline。 |
| 90 s | 不再开始处理新的文件。剩余的每个文件都会以流的形式返回一个明确的失败,带有 DIAG_BUILD_DEADLINE,并注明 N 个文件中完成了多少个,随后流正常关闭。 |
如果你触及了第二个预算,就拆分批次。
诊断代码
错误除了 HTTP 状态外,还携带一个稳定的 code,与引擎自身的诊断同属 DIAG_* 命名空间。客户端必须处理的代码如下:
| 代码 | 状态 | 含义 |
|---|---|---|
DIAG_INVALID_KEY | 401 | 密钥缺失或无法识别。 |
DIAG_KEY_REVOKED | 401 | 密钥已被撤销。 |
DIAG_KEY_EXPIRED | 401 | 密钥已超过其 expiresAt。 |
DIAG_KEY_OUT_OF_SCOPE | 403 | 密钥有效,但从其 allowedIPs 列表之外的 IP 使用。 |
DIAG_CLIENT_UPGRADE_REQUIRED | 426 | 客户端低于配置的最低版本。携带 details.minVersion。参见客户端兼容性。 |
DIAG_API_SUNSET | 410 | 此 API 版本已停用。参见客户端兼容性。 |
DIAG_ENTITLEMENT_LAPSED | 402 | 订阅付款逾期。携带 graceUntil 和 upgradeUrl。 |
DIAG_QUOTA_EXHAUSTED | 402 | 配额 + Extra MB(如已启用,再加上超额上限)不足以支付这次构建。携带 remainingMb、requestedMb,以及一个区分“配额用尽”和“达到超额上限”的 reason。 |
DIAG_RATE_LIMITED | 429 | 见上文速率限制。携带 retryAfterSeconds。 |
DIAG_RATE_CHECK_UNAVAILABLE | 503 | 速率限制检查失败;失败即中止。请重试。 |
DIAG_INVALID_MANIFEST | 400 | multipart 请求体格式错误、缺少 batch 部分,或清单未通过校验。 |
DIAG_FILE_TOO_LARGE | 413 | 某个文件超过 50 MB。 |
DIAG_BATCH_TOO_LARGE | 413 | 超过 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 次构建(以先到者为准),并且可以在项目设置中关闭。完整细节见隐私与数据处理。