Skip to content

First Scan

FileSafety supports two scan modes: sync (get the result in one request) and async (submit now, get results later). You can also scan URLs instead of uploading files. This guide covers all flows in detail.

ModeEndpointBest for
SyncPOST /v1/scanFiles under ~100 MB. Simple integrations where you want results immediately. May timeout for very large files.
AsyncPOST /v1/scan/asyncLarge files or batch processing. Returns a scan ID immediately — get results via polling or webhook.

Both modes accept files up to 1 GB.

The simplest approach. Your server sends the file and receives the complete result in a single request (typically 10–25 seconds).

Terminal window
curl -X POST https://api.filesafety.dev/v1/scan \
-H "x-api-key: YOUR_API_KEY" \
-F "file=@./report.pdf" \
-F 'metadata={"user_id":"usr_123","source":"upload-form"}'
FieldTypeRequiredDescription
filebinaryYesThe file to scan (up to 1 GB).
webhook_urlstringNoURL where scan results will also be POSTed when complete.
metadataJSON objectNoArbitrary key-value pairs returned with the result. Useful for correlating scans with your internal records.
{
"scan_id": "scn_01HX7Z9K3M2N4P5Q6R7S8T9U0V",
"status": "complete",
"verdict": "clean",
"virus": {
"clean": true,
"signature": null
},
"nsfw": {
"clean": true,
"categories": [],
"confidence": 0.01
},
"text": {
"clean": true,
"toxic": { "labels": [], "maxScore": 0.0 },
"pii": { "entities": [], "count": 0 },
"sentiment": { "dominant": "NEUTRAL", "scores": {} }
},
"file_hash": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"completed_at": "2026-03-23T12:00:00Z"
}

If the scan takes longer than ~25 seconds, the response returns "status": "scanning" with a scan_id. Poll GET /v1/scan/{id} for the result.

Use async mode when files may be large, when you want non-blocking behavior, or when uploads come from a browser.

Terminal window
curl -X POST https://api.filesafety.dev/v1/scan/async \
-H "x-api-key: YOUR_API_KEY" \
-F "file=@./large-archive.zip" \
-F "webhook_url=https://your-app.com/webhooks/filesafety" \
-F 'metadata={"user_id":"usr_123"}'
{
"scan_id": "scn_01HX8A1B2C3D4E5F6G7H8I9J0K",
"status": "pending",
"eta_seconds": 15
}

The file is queued for scanning. Retrieve results by polling or via webhook.

Use this when files are uploaded from a browser (avoids proxying through your server), or when you need to generate the upload URL ahead of time.

Send a JSON body without a file:

Terminal window
curl -X POST https://api.filesafety.dev/v1/scan/async \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"webhook_url": "https://your-app.com/webhooks/filesafety",
"metadata": {"user_id": "usr_123"}
}'
{
"scan_id": "scn_01HX8A1B2C3D4E5F6G7H8I9J0K",
"status": "awaiting_upload",
"upload_url": "https://upload.filesafety.dev/scn_01HX8A1B2C3D4E5F6G7H8I9J0K?..."
}

PUT the file to the presigned URL. No API key needed:

Terminal window
curl -X PUT "https://upload.filesafety.dev/scn_01HX8A1B2C3D4E5F6G7H8I9J0K?..." \
-H "Content-Type: application/octet-stream" \
--data-binary @./report.pdf

Once the upload completes, scanning begins automatically. The scan status transitions from awaiting_upload to pending.

The presigned URL can be used directly from client-side JavaScript:

async function uploadToFileSafety(file, uploadUrl) {
const response = await fetch(uploadUrl, {
method: "PUT",
headers: { "Content-Type": "application/octet-stream" },
body: file,
});
if (!response.ok) {
throw new Error(`Upload failed: ${response.status}`);
}
}

Instead of uploading a file, you can send a URL for FileSafety to fetch and scan. This works with both sync and async modes.

Terminal window
curl -X POST https://api.filesafety.dev/v1/scan \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/files/report.pdf"}'

Returns the full scan result directly (same response format as file upload).

Terminal window
curl -X POST https://api.filesafety.dev/v1/scan/async \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/files/report.pdf", "webhook_url": "https://your-app.com/webhooks/filesafety"}'

Returns a scan_id immediately. Poll or use webhooks for results.

FileSafety automatically determines which scans to run based on the uploaded file size. There is no scan_types parameter — all applicable scans run automatically.

File SizeScans Applied
Up to 10 MBMalware detection + Content analysis (images) + Content analysis (text)
10 MB – 100 MBMalware detection + Content analysis (text)
100 MB – 1 GBMalware detection only
Over 1 GBRejected (file too large)

Every plan includes all scan types at no extra cost.

The metadata field accepts any JSON object up to 4 KB. It is stored with the scan and returned in both poll responses and webhook payloads:

{
"metadata": {
"user_id": "usr_123",
"upload_source": "profile-picture",
"original_filename": "avatar.png"
}
}

Use metadata to correlate scan results with your internal systems without needing to maintain a separate mapping.

Check the scan status by ID:

Terminal window
curl https://api.filesafety.dev/v1/scan/scn_01HX7Z9K3M2N4P5Q6R7S8T9U0V \
-H "x-api-key: YOUR_API_KEY"

Poll every 2-3 seconds. Most scans complete within 15 seconds. See Scan Status for the full response schema.

When the scan completes, FileSafety POSTs the result to your webhook_url:

{
"event": "scan.complete",
"scan_id": "scn_01HX7Z9K3M2N4P5Q6R7S8T9U0V",
"verdict": "clean",
"virus": {
"clean": true,
"signature": null
},
"nsfw": {
"clean": true,
"categories": [],
"confidence": 0.01
},
"text": {
"clean": true,
"toxic": { "labels": [], "maxScore": 0.0 },
"pii": { "entities": [], "count": 0 },
"sentiment": { "dominant": "NEUTRAL", "scores": {} }
},
"file_hash": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"metadata": {
"user_id": "usr_123"
},
"completed_at": "2026-03-23T12:00:00Z"
}

Webhooks are the recommended approach for production use. See Webhook Integration for implementation details.

If you upload a file that has already been scanned, FileSafety returns the cached result immediately:

{
"scan_id": "scn_01HX7Z9K3M2N4P5Q6R7S8T9U0V",
"status": "complete",
"verdict": "clean",
"deduplicated": true
}

Deduplication is based on the cryptographic hash of the file contents. Deduplicated scans still count toward your monthly quota.