> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://api.docs.modulards.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://api.docs.modulards.com/_mcp/server.

# Launch site scans

POST https://api.modulards.com/api/public/v1/site-scans
Content-Type: application/json

Launches a malware scan on an explicit selection of websites, one scan per website, over each website's configured areas unless `included` names them (`core`, `plugins`, `themes`, `mu_plugins`, `content`, `uploads`, `database`); each website's own exclusions always apply. Answers **202** with `data` as `site-scans` resources - one per website the preflight accepted - and `meta.skipped` for every website it refused.

**Body** (JSON):
- `sites` (array of website ids, required, 1 to 200).
- `included` (array of string, optional) - areas to scan instead of each website's configured ones.

A website that is disconnected, has no Malware Scanner configuration, is not yet registered with the scanning provider, already has a scan running, or has no scan quota left for any of the requested areas this period is answered in `meta.skipped` with a `reason_code` (`SITE_UNREACHABLE`, `UNSUPPORTED`, `CONFLICT`, `QUOTA_EXCEEDED` - the last carries the reset date) instead of failing the call. When only one of the requested areas has quota left, that website's scan still runs, on that area alone, and the response carries a `meta.warnings` entry naming the area left out.

A manual scan uses the website's scan quota and does not move its scheduled scan; a scan can take hours - follow the launched ids with "List site scans" (`filter[id][]`) and read `status`/`verdict`.

Requires a full-access token (read-only tokens answer 403).

Reference: https://api.docs.modulards.com/modular-ds-public-api/site-scans/launch-site-scans

## Authentication

- `Authorization` header (bearer token, required) — Personal access token created in the Modular DS dashboard; read-only tokens can only call GET endpoints.

## Request

### Body (application/json)

This endpoint expects an object.

- `included` (list of string, optional)
- `sites` (list of double, optional)

## Response

### 202

202 - Scans launched, one skipped

- `data` (list of object, optional)
  - `attributes` (object, optional)
    - `comment` (any, optional, nullable)
    - `created_at` (string, optional)
    - `db_threats_detected` (double, optional)
    - `files_clean` (double, optional)
    - `files_infected` (double, optional)
    - `files_malicious` (double, optional)
    - `files_suspicious` (double, optional)
    - `has_threats` (boolean, optional)
    - `method` (string, optional)
    - `omitted` (list of any, optional)
    - `site_id` (double, optional)
    - `status` (string, optional)
    - `updated_at` (string, optional)
    - `verdict` (string, optional)
  - `id` (string, optional)
  - `links` (object, optional)
    - `self` (string, optional)
  - `type` (string, optional)
- `jsonapi` (object, optional)
  - `version` (string, optional)
- `meta` (object, optional)
  - `skipped` (list of object, optional)
    - `message` (string, optional)
    - `reason_code` (string, optional)
    - `site_id` (double, optional)
    - `site_name` (string, optional)

## Examples

### 202 - Scans launched, one skipped

**Request**

```json
undefined
```

**Response**

```json
{
  "data": [
    {
      "attributes": {
        "comment": null,
        "created_at": "2026-09-18T11:00:00.000000Z",
        "db_threats_detected": 0,
        "files_clean": 0,
        "files_infected": 0,
        "files_malicious": 0,
        "files_suspicious": 0,
        "has_threats": false,
        "method": "manual",
        "omitted": [],
        "site_id": 101,
        "status": "pending",
        "updated_at": "2026-09-18T11:00:00.000000Z",
        "verdict": "pending"
      },
      "id": "3050",
      "links": {
        "self": "https://api.modulards.com/api/public/v1/site-scans/3050"
      },
      "type": "site-scans"
    }
  ],
  "jsonapi": {
    "version": "1.1"
  },
  "meta": {
    "skipped": [
      {
        "message": "This website has no malware scans left for the requested areas until 2026-10-01.",
        "reason_code": "QUOTA_EXCEEDED",
        "site_id": 102,
        "site_name": "Acme Blog"
      }
    ]
  }
}
```

**SDK Code**

```python 202 - Scans launched, one skipped
import requests

url = "https://api.modulards.com/api/public/v1/site-scans"

headers = {"Authorization": "Bearer <token>"}

response = requests.post(url, headers=headers)

print(response.json())
```

```javascript 202 - Scans launched, one skipped
const url = 'https://api.modulards.com/api/public/v1/site-scans';
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go 202 - Scans launched, one skipped
package main

import (
	"fmt"
	"net/http"
	"io"
)

func main() {

	url := "https://api.modulards.com/api/public/v1/site-scans"

	req, _ := http.NewRequest("POST", url, nil)

	req.Header.Add("Authorization", "Bearer <token>")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby 202 - Scans launched, one skipped
require 'uri'
require 'net/http'

url = URI("https://api.modulards.com/api/public/v1/site-scans")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'

response = http.request(request)
puts response.read_body
```

```java 202 - Scans launched, one skipped
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.modulards.com/api/public/v1/site-scans")
  .header("Authorization", "Bearer <token>")
  .asString();
```

```php 202 - Scans launched, one skipped
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.modulards.com/api/public/v1/site-scans', [
  'headers' => [
    'Authorization' => 'Bearer <token>',
  ],
]);

echo $response->getBody();
```

```csharp 202 - Scans launched, one skipped
using RestSharp;

var client = new RestClient("https://api.modulards.com/api/public/v1/site-scans");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
IRestResponse response = client.Execute(request);
```

```swift 202 - Scans launched, one skipped
import Foundation

let headers = ["Authorization": "Bearer <token>"]

let request = NSMutableURLRequest(url: NSURL(string: "https://api.modulards.com/api/public/v1/site-scans")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

### Launch site scans

**Request**

```json
{
  "included": [
    "core",
    "database"
  ],
  "sites": [
    101,
    102
  ]
}
```

**Response**

```json
{
  "data": [
    {
      "attributes": {
        "comment": null,
        "created_at": "2026-09-18T11:00:00.000000Z",
        "db_threats_detected": 0,
        "files_clean": 0,
        "files_infected": 0,
        "files_malicious": 0,
        "files_suspicious": 0,
        "has_threats": false,
        "method": "manual",
        "omitted": [],
        "site_id": 101,
        "status": "pending",
        "updated_at": "2026-09-18T11:00:00.000000Z",
        "verdict": "pending"
      },
      "id": "3050",
      "links": {
        "self": "https://api.modulards.com/api/public/v1/site-scans/3050"
      },
      "type": "site-scans"
    }
  ],
  "jsonapi": {
    "version": "1.1"
  },
  "meta": {
    "skipped": [
      {
        "message": "This website has no malware scans left for the requested areas until 2026-10-01.",
        "reason_code": "QUOTA_EXCEEDED",
        "site_id": 102,
        "site_name": "Acme Blog"
      }
    ]
  }
}
```

**SDK Code**

```python Launch site scans
import requests

url = "https://api.modulards.com/api/public/v1/site-scans"

payload = {
    "included": ["core", "database"],
    "sites": [101, 102]
}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript Launch site scans
const url = 'https://api.modulards.com/api/public/v1/site-scans';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"included":["core","database"],"sites":[101,102]}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go Launch site scans
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.modulards.com/api/public/v1/site-scans"

	payload := strings.NewReader("{\n  \"included\": [\n    \"core\",\n    \"database\"\n  ],\n  \"sites\": [\n    101,\n    102\n  ]\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby Launch site scans
require 'uri'
require 'net/http'

url = URI("https://api.modulards.com/api/public/v1/site-scans")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"included\": [\n    \"core\",\n    \"database\"\n  ],\n  \"sites\": [\n    101,\n    102\n  ]\n}"

response = http.request(request)
puts response.read_body
```

```java Launch site scans
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.modulards.com/api/public/v1/site-scans")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"included\": [\n    \"core\",\n    \"database\"\n  ],\n  \"sites\": [\n    101,\n    102\n  ]\n}")
  .asString();
```

```php Launch site scans
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.modulards.com/api/public/v1/site-scans', [
  'body' => '{
  "included": [
    "core",
    "database"
  ],
  "sites": [
    101,
    102
  ]
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp Launch site scans
using RestSharp;

var client = new RestClient("https://api.modulards.com/api/public/v1/site-scans");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"included\": [\n    \"core\",\n    \"database\"\n  ],\n  \"sites\": [\n    101,\n    102\n  ]\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift Launch site scans
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "included": ["core", "database"],
  "sites": [101, 102]
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.modulards.com/api/public/v1/site-scans")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```