> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://api.docs.modulards.com/modular-ds-public-api/patch-protect/attack-statistics/retrieve-a-website-s-patch-protect-statistics/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 "} 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 '}}; 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 ") 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 ' 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 response = Unirest.get("https://api.modulards.com/api/public/v1/patchstack-services/site_preset_patchstack/stats") .header("Authorization", "Bearer ") .asString(); ``` ```php 200 - Seven days of blocked attacks request('GET', 'https://api.modulards.com/api/public/v1/patchstack-services/site_preset_patchstack/stats', [ 'headers' => [ 'Authorization' => 'Bearer ', ], ]); 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 "); IRestResponse response = client.Execute(request); ``` ```swift 200 - Seven days of blocked attacks import Foundation let headers = ["Authorization": "Bearer "] 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() ```