概覽
網站檢測會在請求內同步完成;APK 檢測採非同步掃描與簽章回呼。所有檢測端點共用帳號會員頻率視窗。
傳送檢測請求或先直傳 APK
驗證特徵、去重並執行掃描
輪詢 GET /api/v1/jobs/{job_id},或同時接收簽章 Webhook
受理回應包含 job_id,可用 GET /api/v1/jobs/{job_id} 輪詢約 24 小時。callback_url 可選;提供時同時投遞簽章 Webhook。
{
"client_reference": "order-20260711-001",
"callback_url": "https://example.com/webhooks/fvl",
"use_cache": false
}驗證
每次請求使用以下任一標頭。金鑰只在建立時完整顯示,必須保存在伺服器端。
Authorization: Bearer fvl_xxxX-API-Key: fvl_xxx請勿將 API 金鑰寫入瀏覽器、行動應用程式、公開儲存庫或日誌。
額度與頻率
API 僅開放給 VIP+、SVIP、SVIP+ 與管理員;同一帳號的所有 API Key 共用頻率視窗。
| 身分組 | 最短間隔 | R2 每日上傳額度 |
|---|---|---|
| VIP+ | 10 分鐘 | 2 GiB |
| Pro | 3 分鐘 | 5 GiB |
| SVIP | 5 分鐘 | 10 GiB |
| SVIP+ | 1 分鐘 | 30 GiB |
| 管理員 | 不限 | 不限 |
端點
除預簽章 R2 PUT 步驟外,所有請求與回應皆使用 JSON。
/api/v1/url-scan同步檢測網站傳入 url,在同一回應取得風險結果與扣次後 quota;不使用 Webhook。
curl -X POST https://www.fuckviruslabel.com/api/v1/url-scan \
-H "Authorization: Bearer fvl_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/path"}'{
"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"
}
}
}/api/v1/apk/uploads建立 APK 上傳工作階段需要 Idempotency-Key、.apk 檔名、size、file_md5、expected_sha256 與共用回呼欄位;上傳網址有效 30 分鐘。
curl -X POST https://www.fuckviruslabel.com/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"
}'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。平台驗證物件並提交已預付審查,不會再次扣次。
curl -X POST https://www.fuckviruslabel.com/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"}'/api/v1/apk/fingerprints提交 APK 特徵傳入 package_name、sha256、file_md5、cert_md5 與共用回呼欄位,無需再次上傳 APK。
curl -X POST https://www.fuckviruslabel.com/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 掃描。
curl -X POST https://www.fuckviruslabel.com/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"
}'{
"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"
}
}/api/v1/jobs/{job_id}輪詢非同步任務使用受理回應中的 job_id 查詢 pending / processing / completed / failed;結果約保留 24 小時。
curl -X POST https://www.fuckviruslabel.com/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"
}'{
"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"
}
}簽發前預扣,簽發後不退還
平台先驗證會員、帳號頻率、複檢額度、每日上傳位元組額度與未完成工作階段數,再預扣一次複檢並簽發 URL。未完成、過期、雜湊不符、非法 APK 或佇列失敗均不退還。暫時入列失敗會保留樣本並由持久化任務自動重試。
合法 APK 依 SHA-256 去重封存;無效或不一致樣本進入 quarantine,兩者保留 90 天。未完成上傳 1 天後自動清除。
use_cache 語意
use_cache 位於 JSON 根層,預設 false。R2 上傳無論值為何都會在簽發 URL 前預扣;此欄位僅控制完成後是否可重用結果。
false強制複檢false 會略過既有結果,實際開始掃描時扣一次複檢。
cache_status = bypasstrue允許快取true 命中有效快取時直接回呼且不扣次;未命中或過期時自動複檢。
cache_status = hit | miss簽章 Webhook
Webhook 是 APK 非同步檢測的唯一結果通道。伺服器傳送原始 JSON 位元組、時間戳與 HMAC-SHA256 簽章。
X-FVL-Timestamp: 1720000000X-FVL-Signature: sha256=<hex>- 計算 webhook_secret = lowercase_hex(SHA-256(raw_api_key))。
- 簽章輸入為 X-FVL-Timestamp + "." + raw_body。
- 以 webhook_secret 的 UTF-8 位元組計算 HMAC-SHA256,並以固定時間比較簽章。
- 驗證時間戳並回傳任意 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;重新序列化會改變位元組並導致驗章失敗。
冪等與去重
建立上傳工作階段、完成上傳、特徵檢測與套件名稱檢測必須提供 Idempotency-Key。
錯誤碼
同步錯誤使用 HTTP 狀態碼與統一 JSON。非同步 APK 審查失敗透過 scan.failed Webhook 回傳。
| HTTP | 含義 | 處理方式 |
|---|---|---|
400 | 請求或欄位無效 | 修正 JSON、雜湊、套件名稱或回呼網址。 |
401 | 驗證失敗 | 檢查 API 金鑰。 |
403 | 無權呼叫 | 檢查會員方案與到期時間。 |
409 | 狀態衝突 | 檢查冪等金鑰或上傳狀態。 |
429 | 額度或頻率受限 | 依 Retry-After 稍後重試。 |
503 | 服務暫時無法使用 | 依端點規則以相同冪等金鑰重試。 |
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://www.fuckviruslabel.com/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://www.fuckviruslabel.com/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())變更記錄
檢測 API 首次發布:網站檢測、R2 上傳、APK 特徵、套件名稱檢測、快取、冪等與簽章 Webhook。