> 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.

# Retrieve a website's Patch & Protect statistics

GET https://api.modulards.com/api/public/v1/patchstack-services/{site_preset_patchstack}/stats

Returns the attacks Patch & Protect blocked on one website: how many per day, the addresses that insisted the most with their country, and the rules that fired, each with its total. Use it to show a customer what the protection did over a week. The id in the path is the id of the website's protection record: read it from the `site_preset_patchstack` include of "Retrieve a website" or "List websites".

`start_date` and `end_date` are whole days, never in the future, and both optional: the default is the last seven whole days, ending today.

That default is the period kept ready. Modular DS stores one period per website and refreshes it regularly, so a read of the default range comes from what is stored and `cached_at` says when it was read. Any other period is read from the security provider, stored in place of the previous one and answered, which takes longer.

The figures need the add-on subscribed, the protection on the website and your role must be allowed to read statistics; without them the call answers 404. A website whose protection is not enabled yet has nothing to read and answers empty lists. Unlike most requests, the answer is the statistics on their own, not a JSON:API document.

Reference: https://api.docs.modulards.com/modular-ds-public-api/patch-protect/attack-statistics/retrieve-a-website-s-patch-protect-statistics

## Authentication

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

## Request

### Path parameters

- `site_preset_patchstack` (string, required)

### Query parameters

- `start_date` (string, optional) — First whole day to count, as `YYYY-MM-DD`, never in the future and not after `end_date`. Defaults to six days before `end_date`, so the default range is seven days counting both ends.
- `end_date` (string, optional) — Last whole day to count, as `YYYY-MM-DD`. Defaults to today and is never in the future.

## Response

### 200

200 - Seven days of blocked attacks

- `attacks` (list of ApiPublicV1PatchstackServicesSitePresetPatchstackStatsGetResponsesContentApplicationJsonSchemaAttacksItems, optional)
- `cached_at` (string, optional, nullable) — When the figures were read from the security provider (ISO 8601); null when nothing has been read.
- `end_date` (string, optional, nullable) — Last day the figures cover, as `YYYY-MM-DD`.
- `ips` (list of ApiPublicV1PatchstackServicesSitePresetPatchstackStatsGetResponsesContentApplicationJsonSchemaIpsItems, optional)
- `rules` (list of ApiPublicV1PatchstackServicesSitePresetPatchstackStatsGetResponsesContentApplicationJsonSchemaRulesItems, optional)
- `start_date` (string, optional, nullable) — First day the figures cover, as `YYYY-MM-DD`.

## Types

### ApiPublicV1PatchstackServicesSitePresetPatchstackStatsGetResponsesContentApplicationJsonSchemaAttacksItems

- `date` (string, optional) — Day, as `YYYY-MM-DD`.
- `total` (integer, optional) — Attacks blocked that day.

### ApiPublicV1PatchstackServicesSitePresetPatchstackStatsGetResponsesContentApplicationJsonSchemaIpsItems

- `country` (ApiPublicV1PatchstackServicesSitePresetPatchstackStatsGetResponsesContentApplicationJsonSchemaIpsItemsCountry, optional)
- `ip` (string, optional) — Address that attacked the website.
- `total` (integer, optional) — Attacks blocked from that address.

### ApiPublicV1PatchstackServicesSitePresetPatchstackStatsGetResponsesContentApplicationJsonSchemaRulesItems

- `description` (string, optional) — Description of the rule that fired.
- `total` (integer, optional) — Times the rule fired.

### ApiPublicV1PatchstackServicesSitePresetPatchstackStatsGetResponsesContentApplicationJsonSchemaIpsItemsCountry

- `code` (string, optional) — ISO code of the country.
- `name` (string, optional) — Country of the address.

## Examples

**Response**

```json
{
  "attacks": [
    {
      "date": "2026-09-18",
      "total": 12
    },
    {
      "date": "2026-09-19",
      "total": 3
    }
  ],
  "cached_at": "2026-09-24T15:00:57.000000Z",
  "end_date": "2026-09-24",
  "ips": [
    {
      "country": {
        "code": "US",
        "name": "United States"
      },
      "ip": "203.0.113.42",
      "total": 9
    }
  ],
  "rules": [
    {
      "description": "SQL injection attempt",
      "total": 15
    }
  ],
  "start_date": "2026-09-18"
}
```

**SDK Code**

```python 200 - Seven days of blocked attacks
import requests

url = "https://api.modulards.com/api/public/v1/patchstack-services/site_preset_patchstack/stats"

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

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

print(response.json())
```

```javascript 200 - Seven days of blocked attacks
const url = 'https://api.modulards.com/api/public/v1/patchstack-services/site_preset_patchstack/stats';
const options = {method: 'GET', 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 200 - Seven days of blocked attacks
package main

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

func main() {

	url := "https://api.modulards.com/api/public/v1/patchstack-services/site_preset_patchstack/stats"

	req, _ := http.NewRequest("GET", 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 200 - Seven days of blocked attacks
require 'uri'
require 'net/http'

url = URI("https://api.modulards.com/api/public/v1/patchstack-services/site_preset_patchstack/stats")

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

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

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

```java 200 - Seven days of blocked attacks
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.get("https://api.modulards.com/api/public/v1/patchstack-services/site_preset_patchstack/stats")
  .header("Authorization", "Bearer <token>")
  .asString();
```

```php 200 - Seven days of blocked attacks
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('GET', 'https://api.modulards.com/api/public/v1/patchstack-services/site_preset_patchstack/stats', [
  'headers' => [
    'Authorization' => 'Bearer <token>',
  ],
]);

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

```csharp 200 - Seven days of blocked attacks
using RestSharp;

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

```swift 200 - Seven days of blocked attacks
import Foundation

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

let request = NSMutableURLRequest(url: NSURL(string: "https://api.modulards.com/api/public/v1/patchstack-services/site_preset_patchstack/stats")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "GET"
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()
```