ServerScan API
ServerScan API
Read and manage your PCI ASV scanning from your own systems.
Authentication. Use your Scan Manager User Token. In Scan Manager, open Access Token and generate a User Token, then send it as Authorization: Bearer <token>. That one token is all you need: you create, replace and revoke it in Scan Manager, and a revoked token stops working here on the next call. Send the User Token, not the API Key shown on the same page. Tokens are never accepted in the query string, and browser sessions are refused. The examples read your token from a SCANMANAGER_TOKEN environment variable.
Paging. Collections take limit (1 to 100, default 25) and return next_cursor. Pass it back as cursor until it is null.
Limits. Each account can make 100 calls a minute and 3,000 calls an hour, whichever token it uses. Both limits are rolling: a call counts for the 60 minutes after it is made, not until the clock reaches the hour. There is no daily limit. Over either one, the answer is 429 rate_limited with a Retry-After header: the number of seconds until your next call will be accepted. Refused calls do not count.
Each IP address can also send about 5 calls a second, with short bursts of up to 30. Faster than that, the answer is a 429 with no Retry-After. Wait a second and slow down.
Wait for Retry-After rather than retrying at once. An address that keeps getting 401 or 429 answers can be blocked from the API for an hour. When you wait for a report document, check every 10 seconds or so. Checking in a tight loop spends your limit.
Base URL: https://www.serverscan.com/api/v1.
Targets
List your scan targets.
GET
/targets
Parameters
limitquery |
integer |
cursorquery |
string next_cursor from the previous page, passed back unchanged. |
curl "https://www.serverscan.com/api/v1/targets" \
-H "Authorization: Bearer $SCANMANAGER_TOKEN"
import os
import requests
resp = requests.request(
"GET",
"https://www.serverscan.com/api/v1/targets",
headers={"Authorization": f"Bearer {os.environ['SCANMANAGER_TOKEN']}"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())
// Node 18 or later, as an ES module (.mjs).
const resp = await fetch("https://www.serverscan.com/api/v1/targets", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.SCANMANAGER_TOKEN}`,
},
});
if (!resp.ok) throw new Error((await resp.json()).error.code);
console.log(await resp.json());
<?php
$client = new GuzzleHttp\Client(['timeout' => 60]);
$resp = $client->request('GET', 'https://www.serverscan.com/api/v1/targets', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('SCANMANAGER_TOKEN'),
],
]);
print_r(json_decode((string) $resp->getBody(), true));
Example response
HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: 9f1c2e7a4b6d8e03
{
"data": [
{
"id": "3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35",
"...": ""
}
],
"next_cursor": null
}
Register a scan target.
POST
/targets
Body application/json
name |
string |
hosts |
string |
comment |
string |
curl -X POST "https://www.serverscan.com/api/v1/targets" \
-H "Authorization: Bearer $SCANMANAGER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Main storefront","hosts":"shop.example.com","comment":"Quarterly external scan"}'
import os
import requests
resp = requests.request(
"POST",
"https://www.serverscan.com/api/v1/targets",
headers={"Authorization": f"Bearer {os.environ['SCANMANAGER_TOKEN']}"},
json={"name": "Main storefront", "hosts": "shop.example.com", "comment": "Quarterly external scan"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())
// Node 18 or later, as an ES module (.mjs).
const resp = await fetch("https://www.serverscan.com/api/v1/targets", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SCANMANAGER_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({"name":"Main storefront","hosts":"shop.example.com","comment":"Quarterly external scan"}),
});
if (!resp.ok) throw new Error((await resp.json()).error.code);
console.log(await resp.json());
<?php
$client = new GuzzleHttp\Client(['timeout' => 60]);
$resp = $client->request('POST', 'https://www.serverscan.com/api/v1/targets', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('SCANMANAGER_TOKEN'),
],
'json' => ['name' => 'Main storefront', 'hosts' => 'shop.example.com', 'comment' => 'Quarterly external scan'],
]);
print_r(json_decode((string) $resp->getBody(), true));
Example response
HTTP/1.1 201 Created
Content-Type: application/json
X-Request-Id: 9f1c2e7a4b6d8e03
{
"data": {
"id": "3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35",
"...": ""
}
}
How many more targets your license allows.
GET
/targets/allow
curl "https://www.serverscan.com/api/v1/targets/allow" \
-H "Authorization: Bearer $SCANMANAGER_TOKEN"
import os
import requests
resp = requests.request(
"GET",
"https://www.serverscan.com/api/v1/targets/allow",
headers={"Authorization": f"Bearer {os.environ['SCANMANAGER_TOKEN']}"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())
// Node 18 or later, as an ES module (.mjs).
const resp = await fetch("https://www.serverscan.com/api/v1/targets/allow", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.SCANMANAGER_TOKEN}`,
},
});
if (!resp.ok) throw new Error((await resp.json()).error.code);
console.log(await resp.json());
<?php
$client = new GuzzleHttp\Client(['timeout' => 60]);
$resp = $client->request('GET', 'https://www.serverscan.com/api/v1/targets/allow', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('SCANMANAGER_TOKEN'),
],
]);
print_r(json_decode((string) $resp->getBody(), true));
Example response
HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: 9f1c2e7a4b6d8e03
{
"data": {
"id": "3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35",
"...": ""
}
}
Read one target.
GET
/targets/{uuid}
Parameters
uuidpath, required |
string (uuid) |
curl "https://www.serverscan.com/api/v1/targets/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35" \
-H "Authorization: Bearer $SCANMANAGER_TOKEN"
import os
import requests
resp = requests.request(
"GET",
"https://www.serverscan.com/api/v1/targets/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35",
headers={"Authorization": f"Bearer {os.environ['SCANMANAGER_TOKEN']}"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())
// Node 18 or later, as an ES module (.mjs).
const resp = await fetch("https://www.serverscan.com/api/v1/targets/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.SCANMANAGER_TOKEN}`,
},
});
if (!resp.ok) throw new Error((await resp.json()).error.code);
console.log(await resp.json());
<?php
$client = new GuzzleHttp\Client(['timeout' => 60]);
$resp = $client->request('GET', 'https://www.serverscan.com/api/v1/targets/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('SCANMANAGER_TOKEN'),
],
]);
print_r(json_decode((string) $resp->getBody(), true));
Example response
HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: 9f1c2e7a4b6d8e03
{
"data": {
"id": "3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35",
"...": ""
}
}
Change a target.
PATCH
/targets/{uuid}
Send only the fields you want to change. Everything else is kept.
Parameters
uuidpath, required |
string (uuid) |
Body application/json
name |
string |
hosts |
string |
comment |
string |
curl -X PATCH "https://www.serverscan.com/api/v1/targets/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35" \
-H "Authorization: Bearer $SCANMANAGER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Main storefront","hosts":"shop.example.com","comment":"Quarterly external scan"}'
import os
import requests
resp = requests.request(
"PATCH",
"https://www.serverscan.com/api/v1/targets/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35",
headers={"Authorization": f"Bearer {os.environ['SCANMANAGER_TOKEN']}"},
json={"name": "Main storefront", "hosts": "shop.example.com", "comment": "Quarterly external scan"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())
// Node 18 or later, as an ES module (.mjs).
const resp = await fetch("https://www.serverscan.com/api/v1/targets/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35", {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.SCANMANAGER_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({"name":"Main storefront","hosts":"shop.example.com","comment":"Quarterly external scan"}),
});
if (!resp.ok) throw new Error((await resp.json()).error.code);
console.log(await resp.json());
<?php
$client = new GuzzleHttp\Client(['timeout' => 60]);
$resp = $client->request('PATCH', 'https://www.serverscan.com/api/v1/targets/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('SCANMANAGER_TOKEN'),
],
'json' => ['name' => 'Main storefront', 'hosts' => 'shop.example.com', 'comment' => 'Quarterly external scan'],
]);
print_r(json_decode((string) $resp->getBody(), true));
Example response
HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: 9f1c2e7a4b6d8e03
{
"data": {
"id": "3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35",
"...": ""
}
}
Remove a target.
DELETE
/targets/{uuid}
Parameters
uuidpath, required |
string (uuid) |
curl -X DELETE "https://www.serverscan.com/api/v1/targets/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35" \
-H "Authorization: Bearer $SCANMANAGER_TOKEN"
import os
import requests
resp = requests.request(
"DELETE",
"https://www.serverscan.com/api/v1/targets/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35",
headers={"Authorization": f"Bearer {os.environ['SCANMANAGER_TOKEN']}"},
timeout=60,
)
resp.raise_for_status()
// Node 18 or later, as an ES module (.mjs).
const resp = await fetch("https://www.serverscan.com/api/v1/targets/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.SCANMANAGER_TOKEN}`,
},
});
if (!resp.ok) throw new Error((await resp.json()).error.code);
<?php
$client = new GuzzleHttp\Client(['timeout' => 60]);
$resp = $client->request('DELETE', 'https://www.serverscan.com/api/v1/targets/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('SCANMANAGER_TOKEN'),
],
]);
Example response
HTTP/1.1 204 No Content
Scans
List your scans.
GET
/scans
Parameters
limitquery |
integer |
cursorquery |
string next_cursor from the previous page, passed back unchanged. |
curl "https://www.serverscan.com/api/v1/scans" \
-H "Authorization: Bearer $SCANMANAGER_TOKEN"
import os
import requests
resp = requests.request(
"GET",
"https://www.serverscan.com/api/v1/scans",
headers={"Authorization": f"Bearer {os.environ['SCANMANAGER_TOKEN']}"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())
// Node 18 or later, as an ES module (.mjs).
const resp = await fetch("https://www.serverscan.com/api/v1/scans", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.SCANMANAGER_TOKEN}`,
},
});
if (!resp.ok) throw new Error((await resp.json()).error.code);
console.log(await resp.json());
<?php
$client = new GuzzleHttp\Client(['timeout' => 60]);
$resp = $client->request('GET', 'https://www.serverscan.com/api/v1/scans', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('SCANMANAGER_TOKEN'),
],
]);
print_r(json_decode((string) $resp->getBody(), true));
Example response
HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: 9f1c2e7a4b6d8e03
{
"data": [
{
"id": "3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35",
"...": ""
}
],
"next_cursor": null
}
Create a scan and start it. Uses one license; send an Idempotency-Key.
POST
/scans
Requires an Idempotency-Key header. Repeating a key with the same body returns the first answer, marked Idempotent-Replayed: true, instead of creating a second scan.
Parameters
Idempotency-Keyheader, required |
string |
Body application/json
name |
string |
comment |
string |
target_id |
string (uuid) |
schedule_id |
string (uuid) |
notification_ids |
array of string (uuid) |
curl -X POST "https://www.serverscan.com/api/v1/scans" \
-H "Authorization: Bearer $SCANMANAGER_TOKEN" \
-H "Idempotency-Key: create-scan-2026-09-18-001" \
-H "Content-Type: application/json" \
-d '{"name":"Q4 external scan","comment":"string","target_id":"3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35","schedule_id":"3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35","notification_ids":["3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35"]}'
import os
import requests
resp = requests.request(
"POST",
"https://www.serverscan.com/api/v1/scans",
headers={"Authorization": f"Bearer {os.environ['SCANMANAGER_TOKEN']}", "Idempotency-Key": "create-scan-2026-09-18-001"},
json={"name": "Q4 external scan", "comment": "string", "target_id": "3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35", "schedule_id": "3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35", "notification_ids": ["3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35"]},
timeout=60,
)
resp.raise_for_status()
print(resp.json())
// Node 18 or later, as an ES module (.mjs).
const resp = await fetch("https://www.serverscan.com/api/v1/scans", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SCANMANAGER_TOKEN}`,
"Idempotency-Key": "create-scan-2026-09-18-001",
"Content-Type": "application/json",
},
body: JSON.stringify({"name":"Q4 external scan","comment":"string","target_id":"3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35","schedule_id":"3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35","notification_ids":["3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35"]}),
});
if (!resp.ok) throw new Error((await resp.json()).error.code);
console.log(await resp.json());
<?php
$client = new GuzzleHttp\Client(['timeout' => 60]);
$resp = $client->request('POST', 'https://www.serverscan.com/api/v1/scans', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('SCANMANAGER_TOKEN'),
'Idempotency-Key' => 'create-scan-2026-09-18-001',
],
'json' => ['name' => 'Q4 external scan', 'comment' => 'string', 'target_id' => '3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35', 'schedule_id' => '3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35', 'notification_ids' => ['3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35']],
]);
print_r(json_decode((string) $resp->getBody(), true));
Example response
HTTP/1.1 201 Created
Content-Type: application/json
X-Request-Id: 9f1c2e7a4b6d8e03
{
"data": {
"id": "3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35"
},
"started": true
}
Read one scan and its status.
GET
/scans/{uuid}
Parameters
uuidpath, required |
string (uuid) |
curl "https://www.serverscan.com/api/v1/scans/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35" \
-H "Authorization: Bearer $SCANMANAGER_TOKEN"
import os
import requests
resp = requests.request(
"GET",
"https://www.serverscan.com/api/v1/scans/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35",
headers={"Authorization": f"Bearer {os.environ['SCANMANAGER_TOKEN']}"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())
// Node 18 or later, as an ES module (.mjs).
const resp = await fetch("https://www.serverscan.com/api/v1/scans/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.SCANMANAGER_TOKEN}`,
},
});
if (!resp.ok) throw new Error((await resp.json()).error.code);
console.log(await resp.json());
<?php
$client = new GuzzleHttp\Client(['timeout' => 60]);
$resp = $client->request('GET', 'https://www.serverscan.com/api/v1/scans/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('SCANMANAGER_TOKEN'),
],
]);
print_r(json_decode((string) $resp->getBody(), true));
Example response
HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: 9f1c2e7a4b6d8e03
{
"data": {
"id": "3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35",
"...": ""
}
}
Stop a running scan.
POST
/scans/{uuid}/stop
Parameters
uuidpath, required |
string (uuid) |
curl -X POST "https://www.serverscan.com/api/v1/scans/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35/stop" \
-H "Authorization: Bearer $SCANMANAGER_TOKEN"
import os
import requests
resp = requests.request(
"POST",
"https://www.serverscan.com/api/v1/scans/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35/stop",
headers={"Authorization": f"Bearer {os.environ['SCANMANAGER_TOKEN']}"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())
// Node 18 or later, as an ES module (.mjs).
const resp = await fetch("https://www.serverscan.com/api/v1/scans/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35/stop", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SCANMANAGER_TOKEN}`,
},
});
if (!resp.ok) throw new Error((await resp.json()).error.code);
console.log(await resp.json());
<?php
$client = new GuzzleHttp\Client(['timeout' => 60]);
$resp = $client->request('POST', 'https://www.serverscan.com/api/v1/scans/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35/stop', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('SCANMANAGER_TOKEN'),
],
]);
print_r(json_decode((string) $resp->getBody(), true));
Example response
HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: 9f1c2e7a4b6d8e03
{
"data": {
"id": "3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35",
"...": ""
}
}
List the reports one scan produced.
GET
/scans/{uuid}/reports
Parameters
uuidpath, required |
string (uuid) |
limitquery |
integer |
cursorquery |
string next_cursor from the previous page, passed back unchanged. |
curl "https://www.serverscan.com/api/v1/scans/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35/reports" \
-H "Authorization: Bearer $SCANMANAGER_TOKEN"
import os
import requests
resp = requests.request(
"GET",
"https://www.serverscan.com/api/v1/scans/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35/reports",
headers={"Authorization": f"Bearer {os.environ['SCANMANAGER_TOKEN']}"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())
// Node 18 or later, as an ES module (.mjs).
const resp = await fetch("https://www.serverscan.com/api/v1/scans/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35/reports", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.SCANMANAGER_TOKEN}`,
},
});
if (!resp.ok) throw new Error((await resp.json()).error.code);
console.log(await resp.json());
<?php
$client = new GuzzleHttp\Client(['timeout' => 60]);
$resp = $client->request('GET', 'https://www.serverscan.com/api/v1/scans/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35/reports', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('SCANMANAGER_TOKEN'),
],
]);
print_r(json_decode((string) $resp->getBody(), true));
Example response
HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: 9f1c2e7a4b6d8e03
{
"data": [
{
"id": "3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35",
"...": ""
}
],
"next_cursor": null
}
Schedules
List your schedules.
GET
/schedules
Parameters
limitquery |
integer |
cursorquery |
string next_cursor from the previous page, passed back unchanged. |
curl "https://www.serverscan.com/api/v1/schedules" \
-H "Authorization: Bearer $SCANMANAGER_TOKEN"
import os
import requests
resp = requests.request(
"GET",
"https://www.serverscan.com/api/v1/schedules",
headers={"Authorization": f"Bearer {os.environ['SCANMANAGER_TOKEN']}"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())
// Node 18 or later, as an ES module (.mjs).
const resp = await fetch("https://www.serverscan.com/api/v1/schedules", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.SCANMANAGER_TOKEN}`,
},
});
if (!resp.ok) throw new Error((await resp.json()).error.code);
console.log(await resp.json());
<?php
$client = new GuzzleHttp\Client(['timeout' => 60]);
$resp = $client->request('GET', 'https://www.serverscan.com/api/v1/schedules', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('SCANMANAGER_TOKEN'),
],
]);
print_r(json_decode((string) $resp->getBody(), true));
Example response
HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: 9f1c2e7a4b6d8e03
{
"data": [
{
"id": "3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35",
"...": ""
}
],
"next_cursor": null
}
Create a schedule.
POST
/schedules
Body application/json
name |
string |
comment |
string |
first_time |
string |
period |
string |
period_unit |
string |
duration |
string |
duration_unit |
string |
timezone |
string |
byday |
string |
bymonthday |
string |
curl -X POST "https://www.serverscan.com/api/v1/schedules" \
-H "Authorization: Bearer $SCANMANAGER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Quarterly","comment":"string","first_time":"string","period":"string","period_unit":"string","duration":"string","duration_unit":"string","timezone":"string","byday":"string","bymonthday":"string"}'
import os
import requests
resp = requests.request(
"POST",
"https://www.serverscan.com/api/v1/schedules",
headers={"Authorization": f"Bearer {os.environ['SCANMANAGER_TOKEN']}"},
json={"name": "Quarterly", "comment": "string", "first_time": "string", "period": "string", "period_unit": "string", "duration": "string", "duration_unit": "string", "timezone": "string", "byday": "string", "bymonthday": "string"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())
// Node 18 or later, as an ES module (.mjs).
const resp = await fetch("https://www.serverscan.com/api/v1/schedules", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SCANMANAGER_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({"name":"Quarterly","comment":"string","first_time":"string","period":"string","period_unit":"string","duration":"string","duration_unit":"string","timezone":"string","byday":"string","bymonthday":"string"}),
});
if (!resp.ok) throw new Error((await resp.json()).error.code);
console.log(await resp.json());
<?php
$client = new GuzzleHttp\Client(['timeout' => 60]);
$resp = $client->request('POST', 'https://www.serverscan.com/api/v1/schedules', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('SCANMANAGER_TOKEN'),
],
'json' => ['name' => 'Quarterly', 'comment' => 'string', 'first_time' => 'string', 'period' => 'string', 'period_unit' => 'string', 'duration' => 'string', 'duration_unit' => 'string', 'timezone' => 'string', 'byday' => 'string', 'bymonthday' => 'string'],
]);
print_r(json_decode((string) $resp->getBody(), true));
Example response
HTTP/1.1 201 Created
Content-Type: application/json
X-Request-Id: 9f1c2e7a4b6d8e03
{
"data": {
"id": "3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35",
"...": ""
}
}
The values a schedule accepts.
GET
/schedules/options
curl "https://www.serverscan.com/api/v1/schedules/options" \
-H "Authorization: Bearer $SCANMANAGER_TOKEN"
import os
import requests
resp = requests.request(
"GET",
"https://www.serverscan.com/api/v1/schedules/options",
headers={"Authorization": f"Bearer {os.environ['SCANMANAGER_TOKEN']}"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())
// Node 18 or later, as an ES module (.mjs).
const resp = await fetch("https://www.serverscan.com/api/v1/schedules/options", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.SCANMANAGER_TOKEN}`,
},
});
if (!resp.ok) throw new Error((await resp.json()).error.code);
console.log(await resp.json());
<?php
$client = new GuzzleHttp\Client(['timeout' => 60]);
$resp = $client->request('GET', 'https://www.serverscan.com/api/v1/schedules/options', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('SCANMANAGER_TOKEN'),
],
]);
print_r(json_decode((string) $resp->getBody(), true));
Example response
HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: 9f1c2e7a4b6d8e03
{
"data": {
"id": "3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35",
"...": ""
}
}
Read one schedule.
GET
/schedules/{uuid}
Parameters
uuidpath, required |
string (uuid) |
curl "https://www.serverscan.com/api/v1/schedules/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35" \
-H "Authorization: Bearer $SCANMANAGER_TOKEN"
import os
import requests
resp = requests.request(
"GET",
"https://www.serverscan.com/api/v1/schedules/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35",
headers={"Authorization": f"Bearer {os.environ['SCANMANAGER_TOKEN']}"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())
// Node 18 or later, as an ES module (.mjs).
const resp = await fetch("https://www.serverscan.com/api/v1/schedules/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.SCANMANAGER_TOKEN}`,
},
});
if (!resp.ok) throw new Error((await resp.json()).error.code);
console.log(await resp.json());
<?php
$client = new GuzzleHttp\Client(['timeout' => 60]);
$resp = $client->request('GET', 'https://www.serverscan.com/api/v1/schedules/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('SCANMANAGER_TOKEN'),
],
]);
print_r(json_decode((string) $resp->getBody(), true));
Example response
HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: 9f1c2e7a4b6d8e03
{
"data": {
"id": "3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35",
"...": ""
}
}
Change a schedule.
PATCH
/schedules/{uuid}
Send only the fields you want to change. Everything else is kept.
Parameters
uuidpath, required |
string (uuid) |
Body application/json
name |
string |
comment |
string |
first_time |
string |
period |
string |
period_unit |
string |
duration |
string |
duration_unit |
string |
timezone |
string |
byday |
string |
bymonthday |
string |
curl -X PATCH "https://www.serverscan.com/api/v1/schedules/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35" \
-H "Authorization: Bearer $SCANMANAGER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Quarterly","comment":"string","first_time":"string","period":"string","period_unit":"string","duration":"string","duration_unit":"string","timezone":"string","byday":"string","bymonthday":"string"}'
import os
import requests
resp = requests.request(
"PATCH",
"https://www.serverscan.com/api/v1/schedules/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35",
headers={"Authorization": f"Bearer {os.environ['SCANMANAGER_TOKEN']}"},
json={"name": "Quarterly", "comment": "string", "first_time": "string", "period": "string", "period_unit": "string", "duration": "string", "duration_unit": "string", "timezone": "string", "byday": "string", "bymonthday": "string"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())
// Node 18 or later, as an ES module (.mjs).
const resp = await fetch("https://www.serverscan.com/api/v1/schedules/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35", {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.SCANMANAGER_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({"name":"Quarterly","comment":"string","first_time":"string","period":"string","period_unit":"string","duration":"string","duration_unit":"string","timezone":"string","byday":"string","bymonthday":"string"}),
});
if (!resp.ok) throw new Error((await resp.json()).error.code);
console.log(await resp.json());
<?php
$client = new GuzzleHttp\Client(['timeout' => 60]);
$resp = $client->request('PATCH', 'https://www.serverscan.com/api/v1/schedules/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('SCANMANAGER_TOKEN'),
],
'json' => ['name' => 'Quarterly', 'comment' => 'string', 'first_time' => 'string', 'period' => 'string', 'period_unit' => 'string', 'duration' => 'string', 'duration_unit' => 'string', 'timezone' => 'string', 'byday' => 'string', 'bymonthday' => 'string'],
]);
print_r(json_decode((string) $resp->getBody(), true));
Example response
HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: 9f1c2e7a4b6d8e03
{
"data": {
"id": "3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35",
"...": ""
}
}
Remove a schedule.
DELETE
/schedules/{uuid}
Parameters
uuidpath, required |
string (uuid) |
curl -X DELETE "https://www.serverscan.com/api/v1/schedules/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35" \
-H "Authorization: Bearer $SCANMANAGER_TOKEN"
import os
import requests
resp = requests.request(
"DELETE",
"https://www.serverscan.com/api/v1/schedules/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35",
headers={"Authorization": f"Bearer {os.environ['SCANMANAGER_TOKEN']}"},
timeout=60,
)
resp.raise_for_status()
// Node 18 or later, as an ES module (.mjs).
const resp = await fetch("https://www.serverscan.com/api/v1/schedules/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.SCANMANAGER_TOKEN}`,
},
});
if (!resp.ok) throw new Error((await resp.json()).error.code);
<?php
$client = new GuzzleHttp\Client(['timeout' => 60]);
$resp = $client->request('DELETE', 'https://www.serverscan.com/api/v1/schedules/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('SCANMANAGER_TOKEN'),
],
]);
Example response
HTTP/1.1 204 No Content
Reports
A report is one finished scan. Read its findings from GET /reports/{uuid}/vulnerabilities, one row per finding, ready for a vulnerability dashboard. Its documents are three PDFs and a spreadsheet, which you ask for and then download:
| Type | Document | Who it is for |
|---|---|---|
detailed | The full scan report, with every finding. The default. | Your team, to fix what the scan found. |
attestation | The Attestation of Scan Compliance. It shows whether the scan passed. | Your acquirer or bank. |
executive | The executive summary. | Management. |
remediation | The vulnerability spreadsheet (.xlsx): every finding with its fix, a worksheet to track fixes, and the hosts scanned. | Your team, to track fixes. |
Getting a document.
- Ask for it:
POST /reports/{uuid}/generate?type=attestation. - Wait until it is ready.
GET /reports/{uuid}lists each document underfiles, by type. Itsstatusisprocessingwhile it is being made anddonewhen it is ready. That usually takes a few seconds. Check every 10 seconds or so, not in a tight loop. - Download it:
GET /reports/{uuid}/pdf?type=attestationfor a PDF, orGET /reports/{uuid}/xlsxfor the spreadsheet.
"files": {
"detailed": { "status": "done", ... },
"attestation": { "status": "processing", ... }
}Asking again makes the document again, and it cannot be downloaded while it is being made. If its status is failed, ask again. Until a document is ready, the download answers 404 not_found with a message saying why. files can also list documents made in Scan Manager that this API does not offer.
List your reports.
GET
/reports
Parameters
limitquery |
integer |
cursorquery |
string next_cursor from the previous page, passed back unchanged. |
curl "https://www.serverscan.com/api/v1/reports" \
-H "Authorization: Bearer $SCANMANAGER_TOKEN"
import os
import requests
resp = requests.request(
"GET",
"https://www.serverscan.com/api/v1/reports",
headers={"Authorization": f"Bearer {os.environ['SCANMANAGER_TOKEN']}"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())
// Node 18 or later, as an ES module (.mjs).
const resp = await fetch("https://www.serverscan.com/api/v1/reports", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.SCANMANAGER_TOKEN}`,
},
});
if (!resp.ok) throw new Error((await resp.json()).error.code);
console.log(await resp.json());
<?php
$client = new GuzzleHttp\Client(['timeout' => 60]);
$resp = $client->request('GET', 'https://www.serverscan.com/api/v1/reports', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('SCANMANAGER_TOKEN'),
],
]);
print_r(json_decode((string) $resp->getBody(), true));
Example response
HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: 9f1c2e7a4b6d8e03
{
"data": [
{
"id": "3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35",
"...": ""
}
],
"next_cursor": null
}
Read one report.
GET
/reports/{uuid}
Includes files: each document made for this report, with its status. See Report documents.
Parameters
uuidpath, required |
string (uuid) |
curl "https://www.serverscan.com/api/v1/reports/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35" \
-H "Authorization: Bearer $SCANMANAGER_TOKEN"
import os
import requests
resp = requests.request(
"GET",
"https://www.serverscan.com/api/v1/reports/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35",
headers={"Authorization": f"Bearer {os.environ['SCANMANAGER_TOKEN']}"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())
// Node 18 or later, as an ES module (.mjs).
const resp = await fetch("https://www.serverscan.com/api/v1/reports/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.SCANMANAGER_TOKEN}`,
},
});
if (!resp.ok) throw new Error((await resp.json()).error.code);
console.log(await resp.json());
<?php
$client = new GuzzleHttp\Client(['timeout' => 60]);
$resp = $client->request('GET', 'https://www.serverscan.com/api/v1/reports/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('SCANMANAGER_TOKEN'),
],
]);
print_r(json_decode((string) $resp->getBody(), true));
Example response
HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: 9f1c2e7a4b6d8e03
{
"data": {
"id": "3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35",
"name": "2026-09-25T14:02:11.000-04:00",
"scan_run_status": "Done",
"creation_time": "2026-09-25T14:02:19.000-04:00",
"end_time": "2026-09-25T14:41:07.000-04:00",
"results_count": {
"high": 0,
"medium": 3,
"low": 2
},
"files": {
"detailed": {
"type": "detailed",
"status": "done",
"filename": "detailed-3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35.pdf",
"created_at": "2026-09-26T20:28:53.908Z",
"updated_at": "2026-09-26T21:21:43.203Z"
},
"attestation": {
"type": "attestation",
"status": "processing",
"filename": "attestation-3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35.pdf",
"created_at": "2026-09-26T20:28:55.120Z",
"updated_at": "2026-09-26T21:56:32.156Z"
}
},
"...": ""
}
}
Read one report in full, findings included.
GET
/reports/{uuid}/details
The report as the scanning platform keeps it, every finding included under results. For the findings as a flat list, one row each, use /vulnerabilities. For the PDFs, see Report documents.
Parameters
uuidpath, required |
string (uuid) |
curl "https://www.serverscan.com/api/v1/reports/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35/details" \
-H "Authorization: Bearer $SCANMANAGER_TOKEN"
import os
import requests
resp = requests.request(
"GET",
"https://www.serverscan.com/api/v1/reports/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35/details",
headers={"Authorization": f"Bearer {os.environ['SCANMANAGER_TOKEN']}"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())
// Node 18 or later, as an ES module (.mjs).
const resp = await fetch("https://www.serverscan.com/api/v1/reports/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35/details", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.SCANMANAGER_TOKEN}`,
},
});
if (!resp.ok) throw new Error((await resp.json()).error.code);
console.log(await resp.json());
<?php
$client = new GuzzleHttp\Client(['timeout' => 60]);
$resp = $client->request('GET', 'https://www.serverscan.com/api/v1/reports/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35/details', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('SCANMANAGER_TOKEN'),
],
]);
print_r(json_decode((string) $resp->getBody(), true));
Example response
HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: 9f1c2e7a4b6d8e03
{
"data": {
"id": "3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35",
"...": ""
}
}
Ask for a report document to be generated.
POST
/reports/{uuid}/generate
Starts making one document. Ask once for each type you want. See Report documents for the four types and how to tell when one is ready.
Parameters
uuidpath, required |
string (uuid) |
typequery |
string Which document. See Report documents. Any other value is 422 invalid_request. One of:
|
curl -X POST "https://www.serverscan.com/api/v1/reports/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35/generate?type=attestation" \
-H "Authorization: Bearer $SCANMANAGER_TOKEN"
import os
import requests
resp = requests.request(
"POST",
"https://www.serverscan.com/api/v1/reports/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35/generate?type=attestation",
headers={"Authorization": f"Bearer {os.environ['SCANMANAGER_TOKEN']}"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())
// Node 18 or later, as an ES module (.mjs).
const resp = await fetch("https://www.serverscan.com/api/v1/reports/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35/generate?type=attestation", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SCANMANAGER_TOKEN}`,
},
});
if (!resp.ok) throw new Error((await resp.json()).error.code);
console.log(await resp.json());
<?php
$client = new GuzzleHttp\Client(['timeout' => 60]);
$resp = $client->request('POST', 'https://www.serverscan.com/api/v1/reports/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35/generate?type=attestation', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('SCANMANAGER_TOKEN'),
],
]);
print_r(json_decode((string) $resp->getBody(), true));
Example response
HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: 9f1c2e7a4b6d8e03
{
"data": []
}
Download a report document as a PDF.
GET
/reports/{uuid}/pdf
Downloads a document once it is ready. See Report documents. Before then this answers 404 not_found, with a message saying why.
Parameters
uuidpath, required |
string (uuid) |
typequery |
string Which PDF. See Report documents. Any other value is 422 invalid_request. One of:
|
curl "https://www.serverscan.com/api/v1/reports/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35/pdf?type=attestation" \
-H "Authorization: Bearer $SCANMANAGER_TOKEN" \
-o attestation.pdf
import os
import requests
resp = requests.request(
"GET",
"https://www.serverscan.com/api/v1/reports/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35/pdf?type=attestation",
headers={"Authorization": f"Bearer {os.environ['SCANMANAGER_TOKEN']}"},
timeout=60,
)
resp.raise_for_status()
open("attestation.pdf", "wb").write(resp.content)
// Node 18 or later, as an ES module (.mjs).
import { writeFile } from "node:fs/promises";
const resp = await fetch("https://www.serverscan.com/api/v1/reports/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35/pdf?type=attestation", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.SCANMANAGER_TOKEN}`,
},
});
if (!resp.ok) throw new Error((await resp.json()).error.code);
await writeFile("attestation.pdf", Buffer.from(await resp.arrayBuffer()));
<?php
$client = new GuzzleHttp\Client(['timeout' => 60]);
$resp = $client->request('GET', 'https://www.serverscan.com/api/v1/reports/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35/pdf?type=attestation', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('SCANMANAGER_TOKEN'),
],
'sink' => 'attestation.pdf',
]);
Example response
HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Disposition: attachment; filename="serverscan-attestation-3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35.pdf"
%PDF-1.7 ...
Download the vulnerability spreadsheet (.xlsx).
GET
/reports/{uuid}/xlsx
Every finding with its fix, a worksheet to track fixes, and the hosts scanned. Generate it first with type=remediation: see Report documents. Before then this answers 404 not_found, with a message saying why.
Parameters
uuidpath, required |
string (uuid) |
curl "https://www.serverscan.com/api/v1/reports/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35/xlsx" \
-H "Authorization: Bearer $SCANMANAGER_TOKEN" \
-o remediation.xlsx
import os
import requests
resp = requests.request(
"GET",
"https://www.serverscan.com/api/v1/reports/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35/xlsx",
headers={"Authorization": f"Bearer {os.environ['SCANMANAGER_TOKEN']}"},
timeout=60,
)
resp.raise_for_status()
open("remediation.xlsx", "wb").write(resp.content)
// Node 18 or later, as an ES module (.mjs).
import { writeFile } from "node:fs/promises";
const resp = await fetch("https://www.serverscan.com/api/v1/reports/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35/xlsx", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.SCANMANAGER_TOKEN}`,
},
});
if (!resp.ok) throw new Error((await resp.json()).error.code);
await writeFile("remediation.xlsx", Buffer.from(await resp.arrayBuffer()));
<?php
$client = new GuzzleHttp\Client(['timeout' => 60]);
$resp = $client->request('GET', 'https://www.serverscan.com/api/v1/reports/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35/xlsx', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('SCANMANAGER_TOKEN'),
],
'sink' => 'remediation.xlsx',
]);
Example response
HTTP/1.1 200 OK
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
Content-Disposition: attachment; filename="serverscan-remediation-3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35.xlsx"
(the workbook)
List one report's vulnerabilities, one row per finding.
GET
/reports/{uuid}/vulnerabilities
Every finding in one report, worst first, one row each. Built for feeding a vulnerability dashboard: list your reports with GET /reports, then read this once for each new report.
Log entries are left out unless you ask for them with min_level=log. They are the scanner's notes, such as which ports were open, at severity 0. meta.counts counts every finding in the report by level, whatever min_level you ask for.
Each row has:
id: this finding. It is new every scan.oid: the check that found it. It is the same in every scan, sohost,portandoidtogether follow one issue from scan to scan.host,port,protocol: where it was found.portisnullfor a finding about the host as a whole.name: the finding's title.family: the kind of check.severity: the CVSS base score, 0 to 10.cvss_vector: the CVSS base vector.level:high(7.0 and up),medium(4.0 to 6.9),low(0.1 to 3.9) orlog(0).cves: CVE ids. Often empty: many checks, such as a missing security header, have none.qod: how sure the scanner is, 0 to 100.summary,insight,affected,impact,detection: what the finding is, why it matters, what is affected, and how it was found.solution,solution_type: how to fix it.evidence: what the scanner saw on this host.references: links about the finding.report_id,scan_id,scanned_at: the report and scan it came from, and when the scan finished, in UTC.
Any text field can be null when the check does not provide it.
Parameters
uuidpath, required |
string (uuid) |
min_levelquery |
string The lowest level to include. Any other value is 422 invalid_request. One of:
|
curl "https://www.serverscan.com/api/v1/reports/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35/vulnerabilities" \
-H "Authorization: Bearer $SCANMANAGER_TOKEN"
import os
import requests
resp = requests.request(
"GET",
"https://www.serverscan.com/api/v1/reports/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35/vulnerabilities",
headers={"Authorization": f"Bearer {os.environ['SCANMANAGER_TOKEN']}"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())
// Node 18 or later, as an ES module (.mjs).
const resp = await fetch("https://www.serverscan.com/api/v1/reports/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35/vulnerabilities", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.SCANMANAGER_TOKEN}`,
},
});
if (!resp.ok) throw new Error((await resp.json()).error.code);
console.log(await resp.json());
<?php
$client = new GuzzleHttp\Client(['timeout' => 60]);
$resp = $client->request('GET', 'https://www.serverscan.com/api/v1/reports/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35/vulnerabilities', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('SCANMANAGER_TOKEN'),
],
]);
print_r(json_decode((string) $resp->getBody(), true));
Example response
HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: 9f1c2e7a4b6d8e03
{
"data": [
{
"id": "b25b6797-995a-441b-9d1c-5543705bc422",
"report_id": "3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35",
"scan_id": "4a0b2c88-cbbb-4985-a2a2-1d9030b58086",
"scanned_at": "2026-09-25T18:41:07Z",
"host": "203.0.113.10",
"port": 443,
"protocol": "tcp",
"name": "SSL/TLS: HTTP Strict Transport Security (HSTS) Missing or Misconfigured",
"severity": 6.5,
"level": "medium",
"cvss_vector": "AV:N/AC:L/Au:S/C:P/I:P/A:P",
"cves": [],
"oid": "1.3.6.1.4.1.25623.1.0.300216",
"family": "Web vulnerability",
"qod": 100,
"summary": "The remote web server is not enforcing HTTP Strict Transport Security (HSTS).",
"insight": null,
"affected": null,
"impact": null,
"detection": null,
"solution": "Enable HSTS or configure the required directives correctly following RFC 6797 and security best practices.",
"solution_type": "Mitigation",
"evidence": "The remote web server is not enforcing HSTS.",
"references": [
"https://owasp.org/www-project-secure-headers/"
]
}
],
"meta": {
"report_id": "3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35",
"scan_id": "4a0b2c88-cbbb-4985-a2a2-1d9030b58086",
"status": "Done",
"scanned_at": "2026-09-25T18:41:07Z",
"hosts_scanned": 1,
"min_level": "low",
"counts": {
"high": 0,
"medium": 3,
"low": 2,
"log": 30
}
}
}
Exceptions
List the exceptions you have raised.
GET
/exceptions
Parameters
limitquery |
integer |
cursorquery |
string next_cursor from the previous page, passed back unchanged. |
curl "https://www.serverscan.com/api/v1/exceptions" \
-H "Authorization: Bearer $SCANMANAGER_TOKEN"
import os
import requests
resp = requests.request(
"GET",
"https://www.serverscan.com/api/v1/exceptions",
headers={"Authorization": f"Bearer {os.environ['SCANMANAGER_TOKEN']}"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())
// Node 18 or later, as an ES module (.mjs).
const resp = await fetch("https://www.serverscan.com/api/v1/exceptions", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.SCANMANAGER_TOKEN}`,
},
});
if (!resp.ok) throw new Error((await resp.json()).error.code);
console.log(await resp.json());
<?php
$client = new GuzzleHttp\Client(['timeout' => 60]);
$resp = $client->request('GET', 'https://www.serverscan.com/api/v1/exceptions', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('SCANMANAGER_TOKEN'),
],
]);
print_r(json_decode((string) $resp->getBody(), true));
Example response
HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: 9f1c2e7a4b6d8e03
{
"data": [
{
"id": "3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35",
"...": ""
}
],
"next_cursor": null
}
Read one exception.
GET
/exceptions/{uuid}
Parameters
uuidpath, required |
string (uuid) |
curl "https://www.serverscan.com/api/v1/exceptions/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35" \
-H "Authorization: Bearer $SCANMANAGER_TOKEN"
import os
import requests
resp = requests.request(
"GET",
"https://www.serverscan.com/api/v1/exceptions/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35",
headers={"Authorization": f"Bearer {os.environ['SCANMANAGER_TOKEN']}"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())
// Node 18 or later, as an ES module (.mjs).
const resp = await fetch("https://www.serverscan.com/api/v1/exceptions/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.SCANMANAGER_TOKEN}`,
},
});
if (!resp.ok) throw new Error((await resp.json()).error.code);
console.log(await resp.json());
<?php
$client = new GuzzleHttp\Client(['timeout' => 60]);
$resp = $client->request('GET', 'https://www.serverscan.com/api/v1/exceptions/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('SCANMANAGER_TOKEN'),
],
]);
print_r(json_decode((string) $resp->getBody(), true));
Example response
HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: 9f1c2e7a4b6d8e03
{
"data": {
"id": "3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35",
"...": ""
}
}
Withdraw an exception.
DELETE
/exceptions/{uuid}
Parameters
uuidpath, required |
string (uuid) |
curl -X DELETE "https://www.serverscan.com/api/v1/exceptions/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35" \
-H "Authorization: Bearer $SCANMANAGER_TOKEN"
import os
import requests
resp = requests.request(
"DELETE",
"https://www.serverscan.com/api/v1/exceptions/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35",
headers={"Authorization": f"Bearer {os.environ['SCANMANAGER_TOKEN']}"},
timeout=60,
)
resp.raise_for_status()
// Node 18 or later, as an ES module (.mjs).
const resp = await fetch("https://www.serverscan.com/api/v1/exceptions/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.SCANMANAGER_TOKEN}`,
},
});
if (!resp.ok) throw new Error((await resp.json()).error.code);
<?php
$client = new GuzzleHttp\Client(['timeout' => 60]);
$resp = $client->request('DELETE', 'https://www.serverscan.com/api/v1/exceptions/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('SCANMANAGER_TOKEN'),
],
]);
Example response
HTTP/1.1 204 No Content
Notifications
List your notification rules.
GET
/notifications
Parameters
limitquery |
integer |
cursorquery |
string next_cursor from the previous page, passed back unchanged. |
curl "https://www.serverscan.com/api/v1/notifications" \
-H "Authorization: Bearer $SCANMANAGER_TOKEN"
import os
import requests
resp = requests.request(
"GET",
"https://www.serverscan.com/api/v1/notifications",
headers={"Authorization": f"Bearer {os.environ['SCANMANAGER_TOKEN']}"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())
// Node 18 or later, as an ES module (.mjs).
const resp = await fetch("https://www.serverscan.com/api/v1/notifications", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.SCANMANAGER_TOKEN}`,
},
});
if (!resp.ok) throw new Error((await resp.json()).error.code);
console.log(await resp.json());
<?php
$client = new GuzzleHttp\Client(['timeout' => 60]);
$resp = $client->request('GET', 'https://www.serverscan.com/api/v1/notifications', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('SCANMANAGER_TOKEN'),
],
]);
print_r(json_decode((string) $resp->getBody(), true));
Example response
HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: 9f1c2e7a4b6d8e03
{
"data": [
{
"id": "3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35",
"...": ""
}
],
"next_cursor": null
}
Create a notification rule.
POST
/notifications
Body application/json
name |
string |
to_address |
string |
status_changed |
string |
severity_at_least |
string |
curl -X POST "https://www.serverscan.com/api/v1/notifications" \
-H "Authorization: Bearer $SCANMANAGER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Security team","to_address":"[email protected]","status_changed":"string","severity_at_least":"string"}'
import os
import requests
resp = requests.request(
"POST",
"https://www.serverscan.com/api/v1/notifications",
headers={"Authorization": f"Bearer {os.environ['SCANMANAGER_TOKEN']}"},
json={"name": "Security team", "to_address": "[email protected]", "status_changed": "string", "severity_at_least": "string"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())
// Node 18 or later, as an ES module (.mjs).
const resp = await fetch("https://www.serverscan.com/api/v1/notifications", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SCANMANAGER_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({"name":"Security team","to_address":"[email protected]","status_changed":"string","severity_at_least":"string"}),
});
if (!resp.ok) throw new Error((await resp.json()).error.code);
console.log(await resp.json());
<?php
$client = new GuzzleHttp\Client(['timeout' => 60]);
$resp = $client->request('POST', 'https://www.serverscan.com/api/v1/notifications', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('SCANMANAGER_TOKEN'),
],
'json' => ['name' => 'Security team', 'to_address' => '[email protected]', 'status_changed' => 'string', 'severity_at_least' => 'string'],
]);
print_r(json_decode((string) $resp->getBody(), true));
Example response
HTTP/1.1 201 Created
Content-Type: application/json
X-Request-Id: 9f1c2e7a4b6d8e03
{
"data": {
"id": "3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35",
"...": ""
}
}
Change a notification rule.
PATCH
/notifications/{uuid}
Send only the fields you want to change. Everything else is kept.
Parameters
uuidpath, required |
string (uuid) |
Body application/json
name |
string |
to_address |
string |
status_changed |
string |
severity_at_least |
string |
curl -X PATCH "https://www.serverscan.com/api/v1/notifications/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35" \
-H "Authorization: Bearer $SCANMANAGER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Security team","to_address":"[email protected]","status_changed":"string","severity_at_least":"string"}'
import os
import requests
resp = requests.request(
"PATCH",
"https://www.serverscan.com/api/v1/notifications/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35",
headers={"Authorization": f"Bearer {os.environ['SCANMANAGER_TOKEN']}"},
json={"name": "Security team", "to_address": "[email protected]", "status_changed": "string", "severity_at_least": "string"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())
// Node 18 or later, as an ES module (.mjs).
const resp = await fetch("https://www.serverscan.com/api/v1/notifications/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35", {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.SCANMANAGER_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({"name":"Security team","to_address":"[email protected]","status_changed":"string","severity_at_least":"string"}),
});
if (!resp.ok) throw new Error((await resp.json()).error.code);
console.log(await resp.json());
<?php
$client = new GuzzleHttp\Client(['timeout' => 60]);
$resp = $client->request('PATCH', 'https://www.serverscan.com/api/v1/notifications/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('SCANMANAGER_TOKEN'),
],
'json' => ['name' => 'Security team', 'to_address' => '[email protected]', 'status_changed' => 'string', 'severity_at_least' => 'string'],
]);
print_r(json_decode((string) $resp->getBody(), true));
Example response
HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: 9f1c2e7a4b6d8e03
{
"data": {
"id": "3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35",
"...": ""
}
}
Remove a notification rule.
DELETE
/notifications/{uuid}
Parameters
uuidpath, required |
string (uuid) |
curl -X DELETE "https://www.serverscan.com/api/v1/notifications/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35" \
-H "Authorization: Bearer $SCANMANAGER_TOKEN"
import os
import requests
resp = requests.request(
"DELETE",
"https://www.serverscan.com/api/v1/notifications/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35",
headers={"Authorization": f"Bearer {os.environ['SCANMANAGER_TOKEN']}"},
timeout=60,
)
resp.raise_for_status()
// Node 18 or later, as an ES module (.mjs).
const resp = await fetch("https://www.serverscan.com/api/v1/notifications/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.SCANMANAGER_TOKEN}`,
},
});
if (!resp.ok) throw new Error((await resp.json()).error.code);
<?php
$client = new GuzzleHttp\Client(['timeout' => 60]);
$resp = $client->request('DELETE', 'https://www.serverscan.com/api/v1/notifications/3f2c8a1e-5b7d-4c9e-9a61-2d4e8f0b7c35', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('SCANMANAGER_TOKEN'),
],
]);
Example response
HTTP/1.1 204 No Content
License
When your scanning license renews or expires, one row per subscription.
GET
/license
Your scanning license, one row per subscription, soonest to expire first. For how many hosts you are scanning now, use GET /targets/allow.
Each row has:
id: this subscription.plan: what was bought.term: how often it renews, one ofquarterly,one_year,two_yearorthree_year.domain: the website or host named when it was bought, ornull.hosts: how many hosts it covers.status: see the table below.renews:truewhen it will renew on its own.started_at: when it began, in UTC.expires_at: when the paid period ends, in UTC. For anactiverow, this is also the renewal date.
| Status | What it means |
|---|---|
active | Paid up. It renews on its own at expires_at. |
past_due | A renewal payment failed. It is paid through expires_at. Update the card in your ServerScan account to renew it. If it stays unpaid, it is canceled. |
canceled | It will not renew. Scans keep running until expires_at. |
expired | It ended at expires_at. Listed only with include=expired. |
meta has:
licensed_hosts: the hosts your license covers right now.next_expires_at: the soonestexpires_atstill to come, ornull.counts: your subscriptions by status, expired ones included.
An expired license stops new scans, not this API. You can still read your reports and download their documents.
Parameters
includequery |
string Send expired to list expired subscriptions too. meta.counts counts them either way. Any other value is 422 invalid_request. One of:
|
curl "https://www.serverscan.com/api/v1/license" \
-H "Authorization: Bearer $SCANMANAGER_TOKEN"
import os
import requests
resp = requests.request(
"GET",
"https://www.serverscan.com/api/v1/license",
headers={"Authorization": f"Bearer {os.environ['SCANMANAGER_TOKEN']}"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())
// Node 18 or later, as an ES module (.mjs).
const resp = await fetch("https://www.serverscan.com/api/v1/license", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.SCANMANAGER_TOKEN}`,
},
});
if (!resp.ok) throw new Error((await resp.json()).error.code);
console.log(await resp.json());
<?php
$client = new GuzzleHttp\Client(['timeout' => 60]);
$resp = $client->request('GET', 'https://www.serverscan.com/api/v1/license', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('SCANMANAGER_TOKEN'),
],
]);
print_r(json_decode((string) $resp->getBody(), true));
Example response
HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: 9f1c2e7a4b6d8e03
{
"data": [
{
"id": "6f1d2c3b-8a47-4e0f-9b25-3c7e1a9d4f60",
"plan": "PCI Scanning Sign-Up",
"term": "one_year",
"domain": "shop.example.com",
"hosts": 1,
"status": "active",
"renews": true,
"started_at": "2025-10-14T16:02:11Z",
"expires_at": "2026-10-14T16:02:11Z"
}
],
"meta": {
"licensed_hosts": 1,
"next_expires_at": "2026-10-14T16:02:11Z",
"counts": {
"active": 1,
"past_due": 0,
"canceled": 0,
"expired": 2
}
}
}
Errors
Branch on code, not on message. Codes keep their meaning and status for the life of v1.
| Status | Code | Meaning |
|---|---|---|
| 401 | missing_token | Send your Scan Manager User Token as "Authorization: Bearer <token>". |
| 401 | invalid_token | That token was not accepted. Generate a new User Token on the Access Token page in Scan Manager. |
| 403 | wrong_auth_method | This API accepts only a bearer token. Browser sessions are refused. |
| 403 | not_enabled | API access is not enabled for this account yet. |
| 403 | forbidden | You do not have access to this. |
| 403 | account_blocked | This ServerScan account is closed. Contact support and quote the request id. |
| 403 | account_not_linked | That token works in Scan Manager, but it is not linked to a ServerScan account. Contact support and quote the request id. |
| 404 | not_found | Not found. |
| 405 | method_not_allowed | Method not allowed on this path. |
| 422 | invalid_request | The request is not valid. |
| 415 | unsupported_media_type | Send the request body as application/json. |
| 413 | payload_too_large | The request body is too large. |
| 409 | idempotency_conflict | That Idempotency-Key was already used with a different request. |
| 409 | idempotency_in_progress | A request with that Idempotency-Key is still being processed. |
| 429 | rate_limited | Too many requests. Slow down and retry after the Retry-After interval. |
| 502 | upstream_error | The scanning platform returned an error. Retry later, and quote the request id if it persists. |
| 504 | upstream_timeout | The scanning platform did not answer in time. Retry later. |
| 502 | upstream_too_large | The scanning platform returned more data than this API forwards. |
| 500 | internal_error | Something went wrong on our side. Quote the request id to support. |
Every error
{
"error": {
"code": "invalid_token",
"message": "That token was not accepted. Generate a new User Token on the Access Token page in Scan Manager.",
"request_id": "9f1c2e7a4b6d8e03"
}
}