Vue d’ensemble
Les analyses d’URL terminent dans la requête ; celles d’APK sont asynchrones. Tous les endpoints partagent la fenêtre de fréquence du compte.
Envoyer la demande ou téléverser l’APK
Valider, dédupliquer et analyser
Livrer le résultat par webhook signé
La réponse d’acceptation inclut job_id. Interrogez GET /api/v1/jobs/{job_id} pendant environ 24 h. callback_url est facultatif ; s’il est fourni, un webhook signé est aussi livré.
{
"client_reference": "order-20260711-001",
"callback_url": "https://example.com/webhooks/fvl",
"use_cache": false
}Authentification
Utilisez l’un des en-têtes à chaque requête. La clé complète n’apparaît qu’à sa création et doit rester côté serveur.
Authorization: Bearer fvl_xxxX-API-Key: fvl_xxxN’intégrez jamais une clé API dans un navigateur, une application mobile, un dépôt public ou des journaux.
Quotas et fréquence
L’API est disponible pour VIP+, SVIP, SVIP+ et les administrateurs. Toutes les clés d’un compte partagent la fréquence.
| Offre | Intervalle minimal | Téléversement R2 quotidien |
|---|---|---|
| VIP+ | 10 minutes | 2 GiB |
| Pro | 3 minutes | 5 GiB |
| SVIP | 5 minutes | 10 GiB |
| SVIP+ | 1 minute | 30 GiB |
| Administrateur | Illimité | Illimité |
Endpoints
Requêtes et réponses utilisent JSON, sauf le PUT R2 présigné.
/api/v1/url-scanAnalyser une URL en synchroneEnvoyez url et recevez le risque et le quota après débit dans la même réponse, sans 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/uploadsCréer une session de téléversement APKNécessite Idempotency-Key, un nom .apk, size, file_md5, expected_sha256 et les champs de rappel. L’URL dure 30 minutes.
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/completeTerminer et soumettre à l’examenEnvoyez upload_token et Idempotency-Key. L’objet est validé puis l’examen prépayé est soumis sans nouveau débit.
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/fingerprintsSoumettre des empreintes APKEnvoyez package_name, sha256, file_md5, cert_md5 et les champs de rappel sans retéléverser l’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/checkVérifier un nom de paquetVérifie rapidement le risque du paquet. Ce n’est pas une analyse APK complète avec hachages et certificat.
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}Interroger une tâche asynchroneUtilisez le job_id de l’acceptation pour lire pending / processing / completed / failed. Les résultats sont conservés environ 24 h.
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"
}
}Précompte avant émission, aucun remboursement après
L’abonnement, la fréquence, le quota, les octets quotidiens et les sessions sont vérifiés avant de débiter une analyse et d’émettre l’URL. Aucun remboursement pour téléversement inachevé ou expiré, hachage incorrect, APK invalide ou échec de file. Les échecs transitoires conservent l’échantillon pour une nouvelle tentative automatique.
Les APK valides sont dédupliqués par SHA-256 et archivés ; les échantillons invalides ou incohérents vont en quarantine. Conservation 90 jours ; les téléversements inachevés sont supprimés après un jour.
Sémantique de use_cache
use_cache est un champ racine, false par défaut. R2 est prépayé avant l’URL quelle que soit sa valeur ; il ne contrôle que la réutilisation du résultat.
falseForcer une nouvelle analysefalse ignore un résultat existant et consomme une analyse quand elle démarre réellement.
cache_status = bypasstrueAutoriser le cachetrue renvoie un cache valide sans débit ; une absence ou expiration relance l’analyse.
cache_status = hit | missWebhook signé
Le webhook est l’unique canal des APK asynchrones. Le serveur envoie le JSON brut, un horodatage et une signature HMAC-SHA256.
X-FVL-Timestamp: 1720000000X-FVL-Signature: sha256=<hex>- Calculez webhook_secret = lowercase_hex(SHA-256(raw_api_key)).
- Signez X-FVL-Timestamp + "." + raw_body.
- Utilisez les octets UTF-8 de webhook_secret pour HMAC-SHA256 et comparez en temps constant.
- Validez l’heure et renvoyez un 2xx ; les autres statuts sont retentés.
{
"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);
}Lisez raw body avant JSON. La resérialisation modifie les octets et invalide la signature.
Idempotence et déduplication
La création et la fin d’un téléversement, les empreintes et les paquets exigent Idempotency-Key.
Erreurs
Les erreurs synchrones utilisent le statut HTTP et un JSON uniforme. Les échecs APK asynchrones arrivent via scan.failed.
| HTTP | Signification | Action |
|---|---|---|
400 | Requête ou champ invalide | Corrigez JSON, hachages, paquet ou rappel. |
401 | Échec d’authentification | Vérifiez la clé API. |
403 | Accès refusé | Vérifiez l’offre et l’expiration. |
409 | Conflit d’état | Vérifiez l’idempotence ou l’état du téléversement. |
429 | Quota ou fréquence dépassé | Respectez Retry-After. |
503 | Service indisponible | Retentez selon l’endpoint avec la même clé. |
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())Journal des modifications
Version initiale : URL, R2, empreintes APK, paquets, cache, idempotence et webhooks signés.