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.
Flow 1: Direct multipart upload
Section titled “Flow 1: Direct multipart upload”The simplest approach. Your server sends the file directly to the API in a single request.
Request
Section titled “Request”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"}'Fields
Section titled “Fields”| Field | Type | Required | Description |
|---|---|---|---|
file | binary | Yes | The file to scan. Sent as multipart form data. |
webhook_url | string | Yes | URL where scan results will be POSTed when complete. |
scan_types | JSON array | No | Types of scans to run. Defaults to ["virus", "nsfw"]. |
metadata | JSON object | No | Arbitrary key-value pairs returned with the result. Useful for correlating scans with your internal records. |
Response
Section titled “Response”{ "scan_id": "scn_01HX7Z9K3M2N4P5Q6R7S8T9U0V", "status": "pending", "eta_seconds": 15}The file is now queued for scanning. Typical processing time is 10-20 seconds.
Flow 2: Presigned URL upload
Section titled “Flow 2: Presigned URL upload”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
Step 1: Request a presigned URL
Section titled “Step 1: Request a presigned URL”Send a JSON body without a file:
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"} }'Response
Section titled “Response”{ "scan_id": "scn_01HX8A1B2C3D4E5F6G7H8I9J0K", "status": "awaiting_upload", "upload_url": "https://upload.filesafety.dev/scn_01HX8A1B2C3D4E5F6G7H8I9J0K?..."}Step 2: Upload the file
Section titled “Step 2: Upload the file”PUT the file to the presigned URL. No API key needed:
curl -X PUT "https://upload.filesafety.dev/scn_01HX8A1B2C3D4E5F6G7H8I9J0K?..." \ -H "Content-Type: application/octet-stream" \ --data-binary @./report.pdfOnce the upload completes, scanning begins automatically. The scan status transitions from awaiting_upload to pending.
Browser upload example
Section titled “Browser upload example”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}`); }}Scan types
Section titled “Scan types”The scan_types field controls which scans run on your file:
| Type | Description |
|---|---|
virus | Detects viruses, malware, trojans, and other threats. |
nsfw | Detects explicit, suggestive, or violent visual content. |
By default, both scan types run on every file. You can request only one:
-F 'scan_types=["virus"]'Every plan includes both scan types at no extra cost.
Using metadata
Section titled “Using metadata”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.
Reading the response
Section titled “Reading the response”Polling
Section titled “Polling”Check the scan status by ID:
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.
Webhook
Section titled “Webhook”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.
Deduplication
Section titled “Deduplication”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.
Next steps
Section titled “Next steps”- Scan API Reference — Full request/response specification
- Code Examples — Node.js and Python implementations
- Plans and Pricing — Quota limits and pricing tiers