概览
网站检测在请求内同步完成;APK 检测采用异步扫描和签名回调。所有对外检测端点按账号共享同一个会员等级频率窗口,调用记录只在平台管理员的聚合历史中留存。
发送检测请求或先直传 APK
校验特征、去重并执行扫描
通过签名 Webhook 投递结果
受理响应返回 job_id,可用 GET /api/v1/jobs/{job_id} 轮询结果(约保留 24 小时)。callback_url 可选;提供时同时投递签名 Webhook。
APK 异步提交公共根字段
| 字段 | 类型 | 说明 |
|---|---|---|
client_reference | string · 必填 | 调用方自己的业务标识,1–128 字符;Webhook / 轮询结果原样返回。 |
callback_url | string · 可选 | 公网 HTTPS 回调地址;省略时仅通过 job_id 轮询取结果。禁止 localhost、私网、IP 字面量。 |
use_cache | boolean · 默认 false | 是否允许直接使用有效缓存。该字段位于请求 JSON 根级。 |
认证
每次请求使用以下任一请求头。密钥只在创建时完整显示,必须存放在服务端。
Authorization: Bearer fvl_xxxX-API-Key: fvl_xxx不要把 API 密钥写入浏览器、移动应用、公开仓库或日志。密钥吊销后,使用该密钥签发但尚未提交的上传会话也应视为不可用。
额度与权限
网站和 APK 检测 API 仅开放给 VIP+、SVIP、SVIP+ 和管理员;强制复查沿用账号现有每日复查额度。
R2 上传是独立的付费审查入口:创建上传会话时先扣一次复查次数并占用频率窗口。即使后续命中缓存或扫描合并,也不返还本次上传预扣。
账号级共享频率
| 身份组 | 最短间隔 | 限制范围 |
|---|---|---|
| VIP+ | 10 分钟 | APK 上传会话创建、APK 特征、包名和网站检测共用;同一账号的所有 API Key 共用。 |
| Pro | 3 分钟 | |
| SVIP | 5 分钟 | |
| SVIP+ | 1 分钟 | |
| 管理员 | 不限制 |
对特征和包名检测,有效缓存命中、24 小时内的 Idempotency-Key 重放,以及合并进同一在途 APK 任务的请求不会开启新窗口。R2 上传在创建会话时开启窗口,完成接口不再计入。超限返回 HTTP 429,并通过 Retry-After 响应头给出最少等待秒数。
单资产复查冷却继续生效:VIP+ 对同一资产为 3 小时,Pro 为 1 小时;SVIP、SVIP+ 和管理员无单资产冷却。该规则与账号级共享频率同时校验。
R2 每日上传字节额度
| 身份组 | 每日额度 | 有效未完成会话 |
|---|---|---|
| VIP+ | 2 GiB | 同一用户最多 1 个 |
| Pro | 5 GiB | |
| SVIP | 10 GiB | |
| SVIP+ | 30 GiB | |
| 管理员 | 不限制 |
网站检测在同步响应中返回 quota;APK 检测可通过 Webhook 和/或 GET /api/v1/jobs/{job_id} 轮询获取。额度或频率受限时接口返回 429。
网站检测
检测 HTTP/HTTPS 网站和域名风险。该端点当前同步执行,并在同一个 HTTP 响应中直接返回 JSON 结果。
/api/v1/url-scan同步检测网站url,支持 HTTP 和 HTTPScurl -X POST https://vendorguard.remiaft.llc/api/v1/url-scan \
-H "Authorization: Bearer fvl_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/path"}'每次实际网站检测会开启账号共享频率窗口;超限时返回 429 和 Retry-After。
同步 JSON 响应
HTTP 200 的 data.result 包含标准化 URL、域名、风险判定和检测时间;data.quota 返回本次扣次后的剩余额度。API 不返回公开报告地址。
{
"code": 0,
"message": "success",
"data": {
"result": {
"status": "completed",
"url": "https://example.com/path",
"domain": "example.com",
"flagged": false,
"mainType": 0,
"mainTypeName": "安全",
"provider": "OPPO",
"websiteType": 0,
"matchUrl": "",
"riskDetail": "",
"cacheStatus": "live",
"checkedAt": 1783765471000,
"createdAt": 1783765471000
},
"quota": {
"identity_label": "VIP+",
"daily_limit": 20,
"daily_used": 1,
"daily_remaining": 19,
"scan_credits": 0,
"total_remaining": 19,
"usage_date": "2026-07-11"
}
}
}R2 直传与审查
大文件直接写入受控 R2 对象,APK 内容不经过网站 Worker。上传后必须调用完成接口才能进入审查队列。
/api/v1/apk/uploads创建上传会话设置 Idempotency-Key,并在公共根字段之外传入 .apk 文件名、最大 500 MiB 的字节数 size、32 位十六进制 file_md5 和必填的 expected_sha256。返回的 upload_url 和 upload_token 有效期为 30 分钟。
平台先校验会员、账号频率、复查额度、每日上传字节额度和未完成会话数,再扣一次复查次数并签发 URL。上传未完成、过期、大小或哈希不符、非法 APK、队列失败均不返还;use_cache 不影响该预扣。瞬时入队失败会保留 R2 样本并由持久化任务自动重投。
curl -X POST https://vendorguard.remiaft.llc/api/v1/apk/uploads \
-H "Authorization: Bearer fvl_xxxxxxxxxxxxxxxx" \
-H "Idempotency-Key: upload-20260711-001" \
-H "Content-Type: application/json" \
-d '{
"client_reference": "order-20260711-001",
"callback_url": "https://api.example.com/webhooks/fvl",
"use_cache": false,
"filename": "release.apk",
"size": 24117384,
"file_md5": "0123456789abcdef0123456789abcdef",
"expected_sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}'按原样使用预签名请求
对 upload_url 执行一次 PUT,并原样携带响应中 required_headers 的 Content-Type、Content-Length、Content-MD5 和 If-None-Match。不要添加 Authorization,且不要复用 URL 写入其他对象。
curl -X PUT "$UPLOAD_URL" \
-H "Content-Type: application/vnd.android.package-archive" \
-H "Content-Length: 24117384" \
-H "Content-MD5: ASNFZ4mrze8BI0VniavN7w==" \
-H "If-None-Match: *" \
--upload-file ./release.apk/api/v1/apk/uploads/complete完成上传并提交审查请求体只传 upload_token,并设置 Idempotency-Key。平台通过 HEAD 校验对象,Scanner 随后重新计算哈希、解析 Manifest 与证书。
本接口只提交已预付的审查,不再次扣除复查次数或占用频率窗口。失败也不返还创建会话时已经扣除的额度;Queue 暂时不可用时仍返回受理结果,D1 待入队记录由 Scanner 每 15 分钟重投,原始样本不会因此删除。
curl -X POST https://vendorguard.remiaft.llc/api/v1/apk/uploads/complete \
-H "Authorization: Bearer fvl_xxxxxxxxxxxxxxxx" \
-H "Idempotency-Key: order-20260711-001" \
-H "Content-Type: application/json" \
-d '{"upload_token":"upl_xxxxxxxxxxxxxxxx"}'合法 APK 归档到按 SHA-256 去重的 apks/,无效或不一致样本进入 quarantine/;两者保留 90 天。未完成上传 1 天后自动清理。
APK 特征检测
已在调用方完成解析时,可直接提交稳定特征,不必再次上传 APK。
/api/v1/apk/fingerprints提交 APK 特征必填 package_name、sha256、file_md5 和 cert_md5。版本、大小、文件名和应用名用于审计与后台搜索。
实际创建新扫描任务时受账号共享频率限制;超限时返回 429 和 Retry-After。
curl -X POST https://vendorguard.remiaft.llc/api/v1/apk/fingerprints \
-H "Authorization: Bearer fvl_xxxxxxxxxxxxxxxx" \
-H "Idempotency-Key: fingerprint-20260711-001" \
-H "Content-Type: application/json" \
-d '{
"client_reference": "fingerprint-20260711-001",
"callback_url": "https://api.example.com/webhooks/fvl",
"use_cache": true,
"package_name": "com.example.app",
"sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"file_md5": "0123456789abcdef0123456789abcdef",
"cert_md5": "abcdef0123456789abcdef0123456789",
"version_code": 108,
"version_name": "1.0.8",
"size": 24117384,
"filename": "release.apk"
}'包名检测
用于快速核验设备侧包名风险。包名会转换为小写并按标准格式校验。
/api/v1/packages/check检测包名仅包名缺少文件哈希与证书维度。如果需要完整判断,请上传 APK 或提交完整特征。
实际创建新包名检测任务时受账号共享频率限制;超限时返回 429 和 Retry-After。
curl -X POST https://vendorguard.remiaft.llc/api/v1/packages/check \
-H "X-API-Key: fvl_xxxxxxxxxxxxxxxx" \
-H "Idempotency-Key: package-20260711-001" \
-H "Content-Type: application/json" \
-d '{
"client_reference": "package-20260711-001",
"callback_url": "https://api.example.com/webhooks/fvl",
"use_cache": false,
"package_name": "com.example.app"
}'统一受理响应
三个 APK 提交接口均返回 HTTP 202。该响应仅表示已接收,最终成功或失败以 Webhook 为准。
{
"code": 0,
"message": "accepted",
"data": {
"client_reference": "order-20260711-001",
"job_id": "acd_xxxxxxxxxxxxxxxx",
"delivery": "webhook_and_poll",
"poll_path": "/api/v1/jobs/acd_xxxxxxxxxxxxxxxx"
}
}use_cache 语义
该布尔字段必须位于请求 JSON 根级,默认值为 false。
false强制复查未传时也走此路径。绕过已有结果,实际发起扫描时扣一次复查次数。
Webhook: cache_status = bypasstrue允许缓存有效缓存命中时直接回调且不扣次;无缓存或已过期时自动复查并扣次。
Webhook: cache_status = hit | miss缓存有效期沿用当前安全、风险、严重和未知状态策略,不作为固定 API 承诺。R2 上传会话无论 use_cache 取值如何,均在签发 URL 前预扣一次审查次数;该字段只决定完成上传后是否允许复用检测结果。
签名 Webhook
Webhook 是 APK 异步检测的唯一结果通道;网站检测不发送 Webhook。服务端发送 JSON 原始字节,并附带时间戳和 HMAC-SHA256 签名。
X-FVL-TimestampX-FVL-Signature: sha256=<hex>- 计算
webhook_secret = lowercase_hex(SHA-256(raw_api_key))。 - 使用未解析的原始请求体构造
X-FVL-Timestamp + "." + raw_body。 - 以
webhook_secret的 UTF-8 字节为 HMAC 密钥计算 SHA-256,并与签名头做恒定时间比较。 - 校验时间戳并返回任意 2xx;非 2xx 会触发重试。
{
"event": "apk.scan.completed",
"client_reference": "order-20260711-001",
"operation": "apk_upload",
"status": "completed",
"cache_status": "bypass",
"result": { "flagged": false, "result_type_name": "安全" },
"quota": { "refresh_daily_remaining": 29, "total_remaining": 29 },
"checked_at": "2026-07-11T10:24:31.000Z"
}import crypto from "node:crypto";
export function verifyFvlWebhook(rawBody, timestamp, signature, apiKey) {
const webhookSecret = crypto
.createHash("sha256")
.update(apiKey, "utf8")
.digest("hex");
const expected = "sha256=" + crypto
.createHmac("sha256", webhookSecret)
.update(timestamp + "." + rawBody)
.digest("hex");
const expectedBytes = Buffer.from(expected, "utf8");
const signatureBytes = Buffer.from(signature, "utf8");
return expectedBytes.length === signatureBytes.length &&
crypto.timingSafeEqual(expectedBytes, signatureBytes);
}必须先读取 raw body 再解析 JSON。重新序列化对象会改变字节序列,导致验签失败。Webhook 采用至少一次投递;调用方应以 client_reference、事件类型和检测时间做幂等处理。
幂等与后台去重
创建上传会话、完成上传、特征检测和包名检测必须提供 Idempotency-Key。
管理员后台按 SHA-256 或标准化包名聚合,只累计调用次数,不为高频查询生成成千上万条记录。
错误码
同步请求错误使用 HTTP 状态码和统一 JSON:{"code": status, "message": "..."}。
| HTTP | 含义 | 处理方式 |
|---|---|---|
400 | 请求体或字段无效 | 修正 JSON、哈希、包名或回调地址后重试。 |
401 | 认证失败 | API 密钥缺失、无效或已吊销。 |
403 | 无权调用 | 账号身份组不支持 API,或会员已到期。 |
409 | 状态冲突 | 幂等键对应不同请求、已有有效未完成上传,或上传会话已完成/失效。 |
429 | 请求受限 | 检测额度或每日上传字节额度已用完,或触发账号级会员频率限制。 |
503 | 服务暂不可用 | 扫描或队列服务不可用;按端点规则重试,已签发的上传额度不返还。 |
APK 格式无效、Manifest 解析失败或声明哈希不一致属于异步审查失败,通过 scan.failed Webhook 返回。
代码示例
下面示例均使用包名检测和根级 use_cache,并以业务引用作为幂等键。
const body = {
client_reference: "package-20260711-001",
callback_url: "https://api.example.com/webhooks/fvl",
use_cache: true,
package_name: "com.example.app"
};
const response = await fetch(
"https://vendorguard.remiaft.llc/api/v1/packages/check",
{
method: "POST",
headers: {
Authorization: "Bearer " + process.env.FVL_API_KEY,
"Content-Type": "application/json",
"Idempotency-Key": body.client_reference
},
body: JSON.stringify(body)
}
);
if (!response.ok) throw new Error(await response.text());
console.log(await response.json());import os
import requests
payload = {
"client_reference": "package-20260711-001",
"callback_url": "https://api.example.com/webhooks/fvl",
"use_cache": True,
"package_name": "com.example.app",
}
response = requests.post(
"https://vendorguard.remiaft.llc/api/v1/packages/check",
headers={
"Authorization": f"Bearer {os.environ['FVL_API_KEY']}",
"Idempotency-Key": payload["client_reference"],
},
json=payload,
timeout=20,
)
response.raise_for_status()
print(response.json())变更日志
纳入同步网站检测,并新增受控 R2 上传、APK 特征检测、包名检测、根级缓存控制、幂等提交和强制签名 Webhook。