Scan Delta
What changed between this scan and the previous scan of the same target — new and removed hosts, ports, services, certificates, findings — as a precomputed summary plus an ordered event list. The diff is computed by the platform when a scan completes; this endpoint returns it, it never computes on the fly.
Combined with GET /my/scans, this is the cheapest
way to stay current: notice a new scan (1 credit), read its delta (1 credit),
and only fetch the full document when the delta says
something you care about moved.
/v1/my/scans/{scan_id}/deltaCost: 1 credit per successful call · Scope: api:org
Path parameters
scan_idstring (uuid)requiredThe newer scan of the pair. The delta compares it against the previous
scan of the same target, named in from_scan_id.
Request
curl "https://api.precursorintelligence.com/functions/v1/my/scans/f63285e2-9c78-4020-b2bb-5c8bd2ca51ed/delta" \
-H "Authorization: Bearer $PRECURSOR_API_KEY"Response
{
"data": {
"scan_id": "f63285e2-9c78-4020-b2bb-5c8bd2ca51ed",
"from_scan_id": "226f9d98-9974-2db7-c307-28f3c5203d10",
"has_delta": true,
"computed_at": "2026-08-06T06:45:12+00:00",
"summary": {
"added": 5, "removed": 0, "changed": 0, "total_changes": 5,
"by_category": {
"ports": { "added": 2, "removed": 0, "changed": 0, "total_before": 34, "total_after": 36 },
"hosts": { "added": 1, "removed": 0, "changed": 0, "total_before": 15, "total_after": 16 },
"vulnerabilities": { "added": 2, "removed": 0, "changed": 0, "total_before": 13, "total_after": 15 }
}
},
"events": [
{
"category": "hosts",
"categoryLabel": "Hosts",
"icon": "fas fa-server",
"type": "added",
"key": "staging.acmecorp.com",
"label": "staging.acmecorp.com",
"message": "New host discovered: staging.acmecorp.com",
"weight": 60,
"changes": null
}
]
},
"meta": {
"request_id": "8b16efb8-9a6c-4ace-b817-11cbee626d08",
"api_version": "1.3.0",
"credits": { "charged": 1, "remaining": 9996 }
}
}Response fields
data.has_deltabooleanoptionalfalse is a real answer, not an error: it means this is the first scan of
its target, or the platform has not computed the diff. When false,
summary, events, from_scan_id and computed_at are all null.
data.from_scan_idstring | nulloptionalThe older scan the comparison ran against.
data.summaryobject | nulloptionalOverall added / removed / changed / total_changes counts, plus a
by_category breakdown (categories: ports, hosts, ip_addresses,
services, ssl_certificates, domains, subdomains, dns_records,
network_ranges, vulnerabilities, vulnerability_instances,
exposed_credentials) with before/after totals per category.
data.eventsarray | nulloptionalThe individual changes. Each event has category, type
(added / removed / changed), key (stable identity of the thing that
changed), label, a human-readable message, a significance weight
(higher = more attention-worthy — an exposed credential outranks a TTL
change), and changes (the changed fields, on type: "changed" events).
An unknown scan_id — or another organisation's — returns 200 OK with
data set to null, and the credit is still charged. Take scan ids from
GET /my/scans.
Errors
| Status | error | When |
|---|---|---|
| 400 | invalid_scan_id | The path segment is not a UUID. Free — rejected before billing. |
| 401 | invalid_authorization | Missing or malformed Authorization header. |
| 401 | unauthorized | Invalid/revoked key, IP not allowlisted, or the key lacks the api:org scope. |
| 402 | insufficient_credits | Organisation credit balance is below 1. |
| 405 | method_not_allowed | Only GET and OPTIONS are accepted. |
| 500 | internal | Unexpected server error (credit auto-refunded). |
See Errors for the full error reference.