{
  "info": {
    "name": "FileSafety API",
    "_postman_id": "filesafety-api-v1",
    "description": "FileSafety API — detect malware, unsafe images, and toxic text content in uploaded files and URLs.\n\n**Scan API** (API key auth via `x-api-key` header): `https://api.filesafety.dev`\n**Dashboard API** (JWT Bearer auth): `https://dashboard-api.filesafety.dev`\n\nSet the `api_key` and `jwt_token` variables in your environment to authenticate.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "apikey",
    "apikey": [
      { "key": "key", "value": "x-api-key" },
      { "key": "value", "value": "{{api_key}}" },
      { "key": "in", "value": "header" }
    ]
  },
  "variable": [
    { "key": "base_url", "value": "https://api.filesafety.dev" },
    { "key": "dashboard_url", "value": "https://dashboard-api.filesafety.dev" },
    { "key": "api_key", "value": "fs_live_YOUR_API_KEY_HERE" },
    { "key": "jwt_token", "value": "" },
    { "key": "scan_id", "value": "" },
    { "key": "upload_url", "value": "" },
    { "key": "key_id", "value": "" },
    { "key": "account_id", "value": "" },
    { "key": "invite_id", "value": "" }
  ],
  "item": [
    {
      "name": "Scanning",
      "item": [
        {
          "name": "Scan file (sync)",
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "const res = pm.response.json();",
                  "if (res.scan_id) pm.collectionVariables.set('scan_id', res.scan_id);"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "body": {
              "mode": "formdata",
              "formdata": [
                { "key": "file", "type": "file", "src": "", "description": "File to scan (up to 1 GB)." },
                { "key": "webhook_url", "value": "https://your-server.com/webhook", "type": "text", "description": "Optional. URL to also receive scan results via webhook.", "disabled": true },
                { "key": "metadata", "value": "{\"user_id\": \"123\"}", "type": "text", "description": "Optional. Custom metadata returned with results.", "disabled": true }
              ]
            },
            "url": {
              "raw": "{{base_url}}/v1/scan",
              "host": ["{{base_url}}"],
              "path": ["v1", "scan"]
            },
            "description": "Upload a file and get the scan result in a single request (typically 10-25 seconds). Scan types are determined automatically based on file size.\n\n**Response includes:**\n- `scan_id` — unique scan identifier\n- `status` — `complete` or `failed`\n- `verdict` — `clean`, `infected`, `nsfw`, `toxic`, `mixed`, or `failed`\n- `virus` / `nsfw` / `text` — detailed scan results"
          }
        },
        {
          "name": "Scan file (async)",
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "const res = pm.response.json();",
                  "if (res.scan_id) pm.collectionVariables.set('scan_id', res.scan_id);"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "body": {
              "mode": "formdata",
              "formdata": [
                { "key": "file", "type": "file", "src": "", "description": "File to scan (up to 1 GB)." },
                { "key": "webhook_url", "value": "https://your-server.com/webhook", "type": "text", "description": "Optional. URL to receive scan results via webhook.", "disabled": true },
                { "key": "metadata", "value": "{\"user_id\": \"123\"}", "type": "text", "description": "Optional. Custom metadata returned with results.", "disabled": true }
              ]
            },
            "url": {
              "raw": "{{base_url}}/v1/scan/async",
              "host": ["{{base_url}}"],
              "path": ["v1", "scan", "async"]
            },
            "description": "Upload a file for asynchronous scanning. Returns a scan_id immediately. Poll GET /v1/scan/{id} for results, or provide a webhook_url.\n\nSupports files up to 1 GB. Scan types are determined automatically based on file size."
          }
        },
        {
          "name": "Scan URL",
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "const res = pm.response.json();",
                  "if (res.scan_id) pm.collectionVariables.set('scan_id', res.scan_id);"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"url\": \"https://example.com/files/report.pdf\"\n}"
            },
            "url": {
              "raw": "{{base_url}}/v1/scan",
              "host": ["{{base_url}}"],
              "path": ["v1", "scan"]
            },
            "description": "Scan a file at a given URL. FileSafety fetches the content and scans it.\n\nUses the sync endpoint — returns the full result in the response. For async URL scanning, use POST /v1/scan/async with the same JSON body."
          }
        },
        {
          "name": "Scan file (presigned URL)",
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "const res = pm.response.json();",
                  "if (res.scan_id) pm.collectionVariables.set('scan_id', res.scan_id);",
                  "if (res.upload_url) pm.collectionVariables.set('upload_url', res.upload_url);"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"webhook_url\": \"https://your-server.com/webhook\",\n  \"metadata\": { \"user_id\": \"123\" }\n}"
            },
            "url": {
              "raw": "{{base_url}}/v1/scan/async",
              "host": ["{{base_url}}"],
              "path": ["v1", "scan", "async"]
            },
            "description": "Get a presigned upload URL for large files (up to 1 GB). Scan types are determined automatically based on file size.\n\n**Steps:**\n1. Call this endpoint to get `upload_url` and `scan_id`\n2. `PUT` your file to `upload_url`\n3. Scanning starts automatically after upload\n4. Results delivered to your `webhook_url` or poll GET /v1/scan/{id}"
          }
        },
        {
          "name": "Upload file to presigned URL",
          "request": {
            "auth": { "type": "noauth" },
            "method": "PUT",
            "header": [],
            "body": {
              "mode": "file",
              "file": { "src": "" }
            },
            "url": {
              "raw": "{{upload_url}}",
              "host": ["{{upload_url}}"]
            },
            "description": "Upload a file to the presigned URL returned by the presigned URL request.\n\nNo authentication needed — the URL is pre-signed.\n\nAfter upload, scanning starts automatically."
          }
        }
      ]
    },
    {
      "name": "Status & Results",
      "item": [
        {
          "name": "Get scan status",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/v1/scan/{{scan_id}}",
              "host": ["{{base_url}}"],
              "path": ["v1", "scan", "{{scan_id}}"]
            },
            "description": "Check the status and results of a scan.\n\n**Status values:**\n- `awaiting_upload` — presigned URL issued, file not yet uploaded\n- `pending` — file uploaded, scan queued\n- `scanning` — scan in progress\n- `complete` — scan finished, results available\n- `failed` — scan failed\n\nWhen `status` is `complete`, the response includes `verdict`, `virus`, `nsfw`, and `text` results."
          }
        },
        {
          "name": "Get usage & quota",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/v1/usage",
              "host": ["{{base_url}}"],
              "path": ["v1", "usage"]
            },
            "description": "Check your current scan usage and quota.\n\n**Response:**\n- `plan` — your current plan (free, starter, growth, pro)\n- `scans_used` — scans used this billing period\n- `scans_quota` — total scans included in your plan\n- `overage_scans` — scans beyond quota (paid plans only)\n- `period_ends` — when the current billing period ends"
          }
        },
        {
          "name": "Health check",
          "request": {
            "auth": { "type": "noauth" },
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/health",
              "host": ["{{base_url}}"],
              "path": ["health"]
            },
            "description": "Health check endpoint used by the ALB. No authentication required.\n\nReturns `{ \"status\": \"ok\" }`."
          }
        }
      ]
    },
    {
      "name": "Dashboard — Profile",
      "description": "Dashboard API endpoints for user profile and account management. Requires JWT Bearer token authentication.",
      "auth": {
        "type": "bearer",
        "bearer": [{ "key": "token", "value": "{{jwt_token}}" }]
      },
      "item": [
        {
          "name": "Get current user",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{dashboard_url}}/v1/dashboard/me",
              "host": ["{{dashboard_url}}"],
              "path": ["v1", "dashboard", "me"]
            },
            "description": "Returns the authenticated user's profile, accounts, API keys, and pending invites."
          }
        },
        {
          "name": "List accounts",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{dashboard_url}}/v1/dashboard/accounts",
              "host": ["{{dashboard_url}}"],
              "path": ["v1", "dashboard", "accounts"]
            },
            "description": "List all accounts the user is a member of."
          }
        },
        {
          "name": "Create account",
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"My New Account\"\n}"
            },
            "url": {
              "raw": "{{dashboard_url}}/v1/dashboard/accounts",
              "host": ["{{dashboard_url}}"],
              "path": ["v1", "dashboard", "accounts"]
            },
            "description": "Create a new account with a default API key.\n\n**Response:** `{ account_id, api_key }`"
          }
        },
        {
          "name": "Change email",
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"new@example.com\"\n}"
            },
            "url": {
              "raw": "{{dashboard_url}}/v1/dashboard/account/email",
              "host": ["{{dashboard_url}}"],
              "path": ["v1", "dashboard", "account", "email"]
            },
            "description": "Update email address across all account memberships, owned accounts, and Stripe. Called after Cognito email verification completes."
          }
        },
        {
          "name": "Export data",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{dashboard_url}}/v1/dashboard/export",
              "host": ["{{dashboard_url}}"],
              "path": ["v1", "dashboard", "export"]
            },
            "description": "Export all account data and scan history as JSON (GDPR right to access)."
          }
        },
        {
          "name": "Close account",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{dashboard_url}}/v1/dashboard/account",
              "host": ["{{dashboard_url}}"],
              "path": ["v1", "dashboard", "account"]
            },
            "description": "Schedule account for deletion. Cancels subscription, deactivates API keys. 30-day grace period to reactivate."
          }
        },
        {
          "name": "Reactivate account",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{dashboard_url}}/v1/dashboard/account/reactivate",
              "host": ["{{dashboard_url}}"],
              "path": ["v1", "dashboard", "account", "reactivate"]
            },
            "description": "Cancel scheduled deletion and reactivate account. Must be within the 30-day grace period."
          }
        }
      ]
    },
    {
      "name": "Dashboard — API Keys",
      "description": "Manage API keys for the current account.",
      "auth": {
        "type": "bearer",
        "bearer": [{ "key": "token", "value": "{{jwt_token}}" }]
      },
      "item": [
        {
          "name": "Create API key",
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "const res = pm.response.json();",
                  "if (res.key_id) pm.collectionVariables.set('key_id', res.key_id);"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "x-account-id", "value": "{{account_id}}", "description": "Account to create key for" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Production Key\",\n  \"enabled_scan_types\": [\"virus\", \"nsfw\", \"text\"],\n  \"default_webhook_url\": \"https://your-server.com/webhook\"\n}"
            },
            "url": {
              "raw": "{{dashboard_url}}/v1/dashboard/api-keys",
              "host": ["{{dashboard_url}}"],
              "path": ["v1", "dashboard", "api-keys"]
            },
            "description": "Create a new API key.\n\n**Response:** `{ api_key, key_id, name }` — the full `api_key` is only shown once."
          }
        },
        {
          "name": "Update API key",
          "request": {
            "method": "PUT",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "x-account-id", "value": "{{account_id}}" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Updated Key Name\",\n  \"active\": true\n}"
            },
            "url": {
              "raw": "{{dashboard_url}}/v1/dashboard/api-keys/{{key_id}}",
              "host": ["{{dashboard_url}}"],
              "path": ["v1", "dashboard", "api-keys", "{{key_id}}"]
            },
            "description": "Update API key properties (name, enabled_scan_types, default_webhook_url, active)."
          }
        },
        {
          "name": "Rotate API key",
          "request": {
            "method": "POST",
            "header": [
              { "key": "x-account-id", "value": "{{account_id}}" }
            ],
            "url": {
              "raw": "{{dashboard_url}}/v1/dashboard/api-keys/{{key_id}}/rotate",
              "host": ["{{dashboard_url}}"],
              "path": ["v1", "dashboard", "api-keys", "{{key_id}}", "rotate"]
            },
            "description": "Rotate an API key. Generates a new key value while preserving the key's configuration. The old key is immediately invalidated.\n\n**Response:** `{ api_key, key_id }` — the new full `api_key` is only shown once."
          }
        },
        {
          "name": "Delete API key",
          "request": {
            "method": "DELETE",
            "header": [
              { "key": "x-account-id", "value": "{{account_id}}" }
            ],
            "url": {
              "raw": "{{dashboard_url}}/v1/dashboard/api-keys/{{key_id}}",
              "host": ["{{dashboard_url}}"],
              "path": ["v1", "dashboard", "api-keys", "{{key_id}}"]
            },
            "description": "Permanently revoke and delete an API key."
          }
        }
      ]
    },
    {
      "name": "Dashboard — Scans",
      "description": "View scan history and details for the current account.",
      "auth": {
        "type": "bearer",
        "bearer": [{ "key": "token", "value": "{{jwt_token}}" }]
      },
      "item": [
        {
          "name": "List scans",
          "request": {
            "method": "GET",
            "header": [
              { "key": "x-account-id", "value": "{{account_id}}" }
            ],
            "url": {
              "raw": "{{dashboard_url}}/v1/dashboard/scans?limit=20",
              "host": ["{{dashboard_url}}"],
              "path": ["v1", "dashboard", "scans"],
              "query": [
                { "key": "limit", "value": "20", "description": "Max results (1-100, default 20)" },
                { "key": "cursor", "value": "", "description": "Pagination cursor from previous response", "disabled": true },
                { "key": "verdict", "value": "", "description": "Filter by verdict: clean, infected, nsfw, mixed", "disabled": true }
              ]
            },
            "description": "List scans for the current account with pagination and optional verdict filter."
          }
        },
        {
          "name": "Get scan by ID",
          "request": {
            "method": "GET",
            "header": [
              { "key": "x-account-id", "value": "{{account_id}}" }
            ],
            "url": {
              "raw": "{{dashboard_url}}/v1/dashboard/scan/{{scan_id}}",
              "host": ["{{dashboard_url}}"],
              "path": ["v1", "dashboard", "scan", "{{scan_id}}"]
            },
            "description": "Get full scan details including virus_result, nsfw_result, and text_result."
          }
        },
        {
          "name": "Create test scan",
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "const res = pm.response.json();",
                  "if (res.scan_id) pm.collectionVariables.set('scan_id', res.scan_id);",
                  "if (res.upload_url) pm.collectionVariables.set('upload_url', res.upload_url);"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "x-account-id", "value": "{{account_id}}" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"file_name\": \"test.pdf\",\n  \"file_size\": 1024\n}"
            },
            "url": {
              "raw": "{{dashboard_url}}/v1/dashboard/test-scan/create",
              "host": ["{{dashboard_url}}"],
              "path": ["v1", "dashboard", "test-scan", "create"]
            },
            "description": "Create a test scan and get a presigned upload URL. Upload the file to `upload_url`, then poll for results.\n\n**Response:** `{ scan_id, upload_url }`"
          }
        },
        {
          "name": "Poll test scan",
          "request": {
            "method": "GET",
            "header": [
              { "key": "x-account-id", "value": "{{account_id}}" }
            ],
            "url": {
              "raw": "{{dashboard_url}}/v1/dashboard/test-scan/poll?scan_id={{scan_id}}",
              "host": ["{{dashboard_url}}"],
              "path": ["v1", "dashboard", "test-scan", "poll"],
              "query": [
                { "key": "scan_id", "value": "{{scan_id}}" }
              ]
            },
            "description": "Poll for test scan results. Returns full scan details when status is `complete` or `failed`."
          }
        }
      ]
    },
    {
      "name": "Dashboard — Team",
      "description": "Manage team members and invites for the current account.",
      "auth": {
        "type": "bearer",
        "bearer": [{ "key": "token", "value": "{{jwt_token}}" }]
      },
      "item": [
        {
          "name": "Get team",
          "request": {
            "method": "GET",
            "header": [
              { "key": "x-account-id", "value": "{{account_id}}" }
            ],
            "url": {
              "raw": "{{dashboard_url}}/v1/dashboard/team",
              "host": ["{{dashboard_url}}"],
              "path": ["v1", "dashboard", "team"]
            },
            "description": "Get all team members and pending invites for the current account."
          }
        },
        {
          "name": "Invite member",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "x-account-id", "value": "{{account_id}}" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"teammate@example.com\",\n  \"role\": \"member\"\n}"
            },
            "url": {
              "raw": "{{dashboard_url}}/v1/dashboard/team/invite",
              "host": ["{{dashboard_url}}"],
              "path": ["v1", "dashboard", "team", "invite"]
            },
            "description": "Invite a user to the account. Roles: `admin`, `member`. Sends an email notification."
          }
        },
        {
          "name": "Cancel invite",
          "request": {
            "method": "DELETE",
            "header": [
              { "key": "x-account-id", "value": "{{account_id}}" }
            ],
            "url": {
              "raw": "{{dashboard_url}}/v1/dashboard/team/invite/{{invite_id}}",
              "host": ["{{dashboard_url}}"],
              "path": ["v1", "dashboard", "team", "invite", "{{invite_id}}"]
            },
            "description": "Cancel a pending team invite."
          }
        },
        {
          "name": "Update member role",
          "request": {
            "method": "PUT",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "x-account-id", "value": "{{account_id}}" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"role\": \"admin\"\n}"
            },
            "url": {
              "raw": "{{dashboard_url}}/v1/dashboard/team/teammate@example.com",
              "host": ["{{dashboard_url}}"],
              "path": ["v1", "dashboard", "team", "teammate@example.com"]
            },
            "description": "Update a team member's role. Roles: `admin`, `member`."
          }
        },
        {
          "name": "Remove member",
          "request": {
            "method": "DELETE",
            "header": [
              { "key": "x-account-id", "value": "{{account_id}}" }
            ],
            "url": {
              "raw": "{{dashboard_url}}/v1/dashboard/team/teammate@example.com",
              "host": ["{{dashboard_url}}"],
              "path": ["v1", "dashboard", "team", "teammate@example.com"]
            },
            "description": "Remove a team member from the account."
          }
        },
        {
          "name": "List my invites",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{dashboard_url}}/v1/dashboard/invites",
              "host": ["{{dashboard_url}}"],
              "path": ["v1", "dashboard", "invites"]
            },
            "description": "List all pending invites for the authenticated user."
          }
        },
        {
          "name": "Accept invite",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{dashboard_url}}/v1/dashboard/invites/{{invite_id}}/accept",
              "host": ["{{dashboard_url}}"],
              "path": ["v1", "dashboard", "invites", "{{invite_id}}", "accept"]
            },
            "description": "Accept a pending team invite. The authenticated user must match the invite's email."
          }
        }
      ]
    },
    {
      "name": "Dashboard — Billing",
      "description": "Billing and checkout for the current account.",
      "auth": {
        "type": "bearer",
        "bearer": [{ "key": "token", "value": "{{jwt_token}}" }]
      },
      "item": [
        {
          "name": "Create checkout session",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "x-account-id", "value": "{{account_id}}" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"plan\": \"starter\",\n  \"interval\": \"month\"\n}"
            },
            "url": {
              "raw": "{{dashboard_url}}/v1/dashboard/create-checkout",
              "host": ["{{dashboard_url}}"],
              "path": ["v1", "dashboard", "create-checkout"]
            },
            "description": "Create a Stripe checkout session for upgrading to a paid plan.\n\n**Plans:** `starter`, `growth`, `pro`\n**Intervals:** `month`, `year`\n\n**Response:** `{ url }` — redirect the user to this Stripe checkout URL."
          }
        }
      ]
    }
  ]
}
