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.
Choosing a scan mode
Section titled “Choosing a scan mode”| Mode | Endpoint | Best for |
|---|---|---|
| Sync | POST /v1/scan | Files under ~100 MB. Simple integrations where you want results immediately. May timeout for very large files. |
| Async | POST /v1/scan/async | Large files or batch processing. Returns a scan ID immediately — get results via polling or webhook. |
Both modes accept files up to 1 GB.
Sync scan — direct file upload
Section titled “Sync scan — direct file upload”The simplest approach. Your server sends the file and receives the complete result in a single request (typically 10–25 seconds).
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 'metadata={"user_id":"usr_123","source":"upload-form"}'Fields
Section titled “Fields”| Field | Type | Required | Description |
|---|---|---|---|
file | binary | Yes | The file to scan (up to 1 GB). |
webhook_url | string | No | URL where scan results will also be POSTed when complete. |
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": "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.
Async scan — file upload
Section titled “Async scan — file upload”Use async mode when files may be large, when you want non-blocking behavior, or when uploads come from a browser.
Direct upload (up to 1 GB)
Section titled “Direct upload (up to 1 GB)”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"}'Response
Section titled “Response”{ "scan_id": "scn_01HX8A1B2C3D4E5F6G7H8I9J0K", "status": "pending", "eta_seconds": 15}The file is queued for scanning. Retrieve results by polling or via webhook.
Presigned URL upload
Section titled “Presigned URL upload”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.
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/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"} }'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}`); }}URL scanning
Section titled “URL scanning”Instead of uploading a file, you can send a URL for FileSafety to fetch and scan. This works with both sync and async modes.
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).
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.
Automatic scan type selection
Section titled “Automatic scan type selection”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 Size | Scans Applied |
|---|---|
| Up to 10 MB | Malware detection + Content analysis (images) + Content analysis (text) |
| 10 MB – 100 MB | Malware detection + Content analysis (text) |
| 100 MB – 1 GB | Malware detection only |
| Over 1 GB | Rejected (file too large) |
Every plan includes all 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, "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.
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