POST /v1/scan
Submit a file for scanning. Supports two modes: direct upload (send the file, get the result in one request) or presigned URL (get an upload URL for large files, poll for results).
POST https://api.filesafety.dev/v1/scanAuthentication
Section titled “Authentication”Requires x-api-key header. See Authentication.
Direct upload
Section titled “Direct upload”Send the file as multipart form data. The API waits for the scan to complete and returns the full result in the response (typically 10–25 seconds).
Best for files under 4MB and simple integrations where you want results in a single request.
Request
Section titled “Request”Content-Type: multipart/form-data
| Field | Type | Required | Description |
|---|---|---|---|
file | binary | Yes | The file to scan (max 4MB via direct upload). For larger files, use the presigned URL flow below. |
webhook_url | string | No | HTTPS URL where scan results will also be POSTed. |
scan_types | JSON string | No | Array of scan types. Options: "virus", "nsfw". Defaults to both. |
metadata | JSON string | No | Arbitrary key-value object (max 4 KB) returned with results. |
Example request
Section titled “Example request”curl -X POST https://api.filesafety.dev/v1/scan \ -H "x-api-key: fs_live_aBcDeFgHiJkLmNoPqRsTuVwXyZ012345" \ -F "file=@./document.pdf"With optional webhook and metadata:
curl -X POST https://api.filesafety.dev/v1/scan \ -H "x-api-key: fs_live_aBcDeFgHiJkLmNoPqRsTuVwXyZ012345" \ -F "file=@./document.pdf" \ -F "webhook_url=https://your-app.com/webhooks/filesafety" \ -F 'metadata={"user_id":"usr_123"}'Response — 200 OK
Section titled “Response — 200 OK”{ "scan_id": "scn_01HX7Z9K3M2N4P5Q6R7S8T9U0V", "status": "complete", "verdict": "clean", "virus": { "clean": true, "signature": null }, "nsfw": { "clean": true, "categories": [], "confidence": 0.01 }, "file_hash": "sha256:e3b0c44298fc1c149afbf4c8...", "completed_at": "2025-01-15T10:30:45.123Z"}| Field | Type | Description |
|---|---|---|
scan_id | string | Unique scan identifier. |
status | string | "complete" or "failed". If the scan takes longer than ~25 seconds, returns "scanning" with the scan_id for polling. |
verdict | string | One of: clean, infected, nsfw, mixed, failed. |
virus | object | Virus scan result. clean: true if no threats found. signature contains the threat name if infected. |
nsfw | object | NSFW detection result. clean: true if safe. categories lists flagged content types. |
file_hash | string | Cryptographic hash of the scanned file. |
completed_at | string | ISO 8601 timestamp of scan completion. |
If the file was previously scanned (matched by hash), the cached result returns instantly.
Presigned URL
Section titled “Presigned URL”Request a presigned upload URL for large files (up to 200MB). After uploading, poll GET /v1/scan/{id} for results, or provide a webhook_url to receive them automatically.
Request
Section titled “Request”Content-Type: application/json
{ "webhook_url": "https://your-app.com/webhooks/filesafety", "scan_types": ["virus", "nsfw"], "metadata": {"user_id": "usr_123"}}| Field | Type | Required | Description |
|---|---|---|---|
webhook_url | string | No | HTTPS URL where scan results will be POSTed on completion. |
scan_types | array | No | Array of scan types. Options: "virus", "nsfw". Defaults to both. |
metadata | object | No | Arbitrary key-value object (max 4 KB) returned with scan results. |
Example request
Section titled “Example request”curl -X POST https://api.filesafety.dev/v1/scan \ -H "x-api-key: fs_live_aBcDeFgHiJkLmNoPqRsTuVwXyZ012345" \ -H "Content-Type: application/json" \ -d '{"webhook_url": "https://your-app.com/webhooks/filesafety"}'Response — 200 OK
Section titled “Response — 200 OK”{ "scan_id": "scn_01HX8A1B2C3D4E5F6G7H8I9J0K", "status": "awaiting_upload", "upload_url": "https://upload.filesafety.dev/scn_01HX8A1B2C3D4E5F6G7H8I9J0K?..."}| Field | Type | Description |
|---|---|---|
scan_id | string | Unique scan identifier. |
status | string | "awaiting_upload" — waiting for the file. |
upload_url | string | Presigned URL. PUT the file here to begin scanning. Expires in 15 minutes. |
Uploading to the presigned URL
Section titled “Uploading to the presigned URL”curl -X PUT "UPLOAD_URL_FROM_RESPONSE" \ -H "Content-Type: application/octet-stream" \ --data-binary @./document.pdfOnce the upload succeeds, scanning begins automatically. Poll GET /v1/scan/{id} for results.
Error responses
Section titled “Error responses”| Status | Error | Cause |
|---|---|---|
400 | "Invalid JSON body" | Malformed JSON in request body. |
400 | "Invalid scan_types" | scan_types contains an unrecognized value. |
401 | "Invalid or missing API key" | Missing or invalid x-api-key header. |
413 | "File too large for direct upload" | File exceeds 4MB. Use the presigned URL flow for larger files. |
429 | "Monthly scan quota exceeded" | Free plan quota reached. Upgrade to continue scanning. |
Verdict reference
Section titled “Verdict reference”| Verdict | Meaning |
|---|---|
clean | No threats detected. |
infected | Virus or malware detected. Check virus.signature for the threat name. |
nsfw | Explicit or suggestive content detected. Check nsfw.categories for details. |
mixed | Both virus and NSFW issues detected. |
failed | Scan could not be completed. Retry the request. |
Scan types
Section titled “Scan types”| Type | What it detects |
|---|---|
virus | Viruses, malware, trojans, ransomware, adware. |
nsfw | Explicit nudity, suggestive content, graphic violence. |
Both scan types are included in every plan at no extra cost.