Skip to content

First Scan

FileSafety supports two ways to submit files for scanning: direct multipart upload (simplest) and presigned URL (best for large files or browser uploads). This guide covers both flows in detail.

The simplest approach. Your server sends the file directly to the API in a single request.

Terminal window
curl -X POST https://api.filesafety.dev/v1/scan \
-H "x-api-key: YOUR_API_KEY" \
-F "file=@./report.pdf" \
-F "webhook_url=https://your-app.com/webhooks/filesafety" \
-F 'scan_types=["virus","nsfw"]' \
-F 'metadata={"user_id":"usr_123","source":"upload-form"}'
FieldTypeRequiredDescription
filebinaryYesThe file to scan. Sent as multipart form data.
webhook_urlstringYesURL where scan results will be POSTed when complete.
scan_typesJSON arrayNoTypes of scans to run. Defaults to ["virus", "nsfw"].
metadataJSON objectNoArbitrary key-value pairs returned with the result. Useful for correlating scans with your internal records.
{
"scan_id": "scn_01HX7Z9K3M2N4P5Q6R7S8T9U0V",
"status": "pending",
"eta_seconds": 15
}

The file is now queued for scanning. Typical processing time is 10-20 seconds.

Use this flow when:

  • Files are uploaded from a browser (avoids proxying through your server)
  • Files are large and you want to decouple the upload from the scan request
  • 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 \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"webhook_url": "https://your-app.com/webhooks/filesafety",
"scan_types": ["virus", "nsfw"],
"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}`);
}
}

The scan_types field controls which scans run on your file:

TypeDescription
virusDetects viruses, malware, trojans, and other threats.
nsfwDetects explicit, suggestive, or violent visual content.

By default, both scan types run on every file. You can request only one:

Terminal window
-F 'scan_types=["virus"]'

Every plan includes both 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,
"engine": "clamav",
"signature": null
},
"nsfw": {
"clean": true,
"categories": [],
"confidence": 0.01
},
"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.