개요
URL 검사는 요청 안에서 끝나며 APK 검사는 비동기입니다. 모든 검사 엔드포인트는 계정 단위 속도 창을 공유합니다.
검사 요청 또는 APK 업로드
검증, 중복 제거 및 검사 실행
서명 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_xxxAPI 키를 브라우저, 모바일 앱, 공개 저장소 또는 로그에 넣지 마십시오.
할당량 및 속도 제한
API는 VIP+, SVIP, SVIP+ 및 관리자에게 제공됩니다. 한 계정의 모든 API 키가 속도 창을 공유합니다.
| 요금제 | 최소 간격 | 일일 R2 업로드 |
|---|---|---|
| VIP+ | 10분 | 2 GiB |
| Pro | 3분 | 5 GiB |
| SVIP | 5분 | 10 GiB |
| SVIP+ | 1분 | 30 GiB |
| 관리자 | 무제한 | 무제한 |
엔드포인트
서명된 R2 PUT 단계를 제외하고 요청과 응답은 JSON입니다.
/api/v1/url-scanURL 동기 검사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/uploadsAPK 업로드 세션 생성Idempotency-Key, .apk 파일명, size, file_md5, expected_sha256 및 공통 콜백 필드가 필요하며 URL은 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/fingerprintsAPK 지문 제출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"
}
}발급 전 선차감, 발급 후 환불 없음
멤버십, 계정 속도, 재검사 할당량, 일일 업로드 바이트 및 미완료 세션을 확인한 뒤 1회를 차감하고 URL을 발급합니다. 미완료, 만료, 해시 불일치, 잘못된 APK 또는 큐 실패에도 환불되지 않습니다. 일시적 큐 실패는 샘플을 보존하고 자동 재시도합니다.
정상 APK는 SHA-256으로 중복 제거해 보관하고 잘못되거나 불일치한 샘플은 quarantine으로 이동합니다. 모두 90일 보존하며 미완료 업로드는 1일 후 삭제합니다.
use_cache 의미
use_cache는 JSON 루트 필드이며 기본값은 false입니다. R2는 값과 관계없이 URL 발급 전에 선차감하고 완료 후 결과 재사용만 제어합니다.
false강제 재검사false는 기존 결과를 우회하고 실제 검사가 시작될 때 1회를 사용합니다.
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);
}JSON 파싱 전에 raw body를 읽으십시오. 재직렬화하면 서명 검증이 실패합니다.
멱등성 및 중복 제거
업로드 생성·완료, 지문 및 패키지 검사에는 Idempotency-Key가 필요합니다.
오류
동기 오류는 HTTP 상태와 통일된 JSON을 사용합니다. 비동기 APK 검토 실패는 scan.failed Webhook으로 전달됩니다.
| HTTP | 의미 | 조치 |
|---|---|---|
400 | 잘못된 요청 또는 필드 | JSON, 해시, 패키지명 또는 콜백 URL을 수정합니다. |
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())변경 기록
초기 릴리스: URL, R2 업로드, APK 지문, 패키지 검사, 캐시, 멱등성 및 서명 Webhook.