API Reference · Your Data

Get a Scan

One whole EdgeProtect scan as a single document, for a single credit: the scan summary with its rollup counts, plus hosts (each with its IP addresses and open ports, and what is listening on them), domains, DNS records, SSL certificates, network ranges, and vulnerabilities (with their instances inline).

This is deliberately the opposite of a one-endpoint-per-table API. Walking the same data as separate hosts/ports/services/DNS/SSL collections would cost 10+ credits per scan; this returns the entire snapshot for 1. The projection is lean — no scanner metadata, raw banners, certificate chains or proof-of-concept text — a typical full document is on the order of 100 kB.

Scans are immutable once complete: fetch a scan once and cache it forever.

GET
/v1/my/scans/{scan_id}

Cost: 1 credit per successful call · Scope: api:org

Path parameters

scan_idstring (uuid)required

A scan id from GET /my/scans.

Query parameters

sectionsstringoptional

Comma-separated projection of which sections to include, from hosts, domains, dns_records, ssl_certificates, network_ranges, vulnerabilities. Omit for everything. The scan summary is always present. Same price either way — this is a bandwidth knob, not a billing one. Unknown or duplicate names are a free 400.

Request

curl "https://api.precursorintelligence.com/functions/v1/my/scans/f63285e2-9c78-4020-b2bb-5c8bd2ca51ed" \
  -H "Authorization: Bearer $PRECURSOR_API_KEY"

Response

Trimmed for length — each array shows one representative entry.

200 OK
{
  "data": {
    "scan": {
      "id": "f63285e2-9c78-4020-b2bb-5c8bd2ca51ed",
      "name": "Scan 06/08/2026 - acmecorp.com",
      "status": "completed",
      "created_at": "2026-08-06T06:12:44+00:00",
      "target": { "id": "73d787fb-…", "name": "Acme Corporation", "value": "acmecorp.com", "type": "domain" },
      "counts": { "hosts": 16, "ports": 36, "services": 36, "domains": 1, "ip_addresses": 17,
                  "dns_records": 34, "ssl_certificates": 13, "network_ranges": 6,
                  "vulnerabilities": 15, "vulnerability_instances": 42, "exposed_credentials": 40 }
    },
    "hosts": [
      {
        "id": "8138cd83-46e2-42c3-bd94-97a2c727ba22",
        "hostname": "vpn",
        "fqdn": "vpn.acmecorp.com",
        "os": { "family": "Linux" },
        "status": "active",
        "last_seen": "2026-08-06T02:40:33+00:00",
        "ip_addresses": [
          { "ip": "203.0.113.65", "version": 4, "city": "London", "country_code": "GB",
            "asn": "AS16509", "isp": "Amazon.com, Inc." }
        ],
        "open_ports": [
          { "ip": "203.0.113.65", "port": 443, "protocol": "tcp",
            "service": "https", "product": "nginx", "version": "1.24.0",
            "ssl": true, "http_status": 200, "http_title": "Acme VPN portal",
            "cpe": ["cpe:2.3:a:f5:nginx:1.24.0:*:*:*:*:*:*:*"] }
        ]
      }
    ],
    "domains": [
      { "id": "…", "domain_name": "acmecorp.com", "domain_type": "primary", "status": "active",
        "registrar": "Example Registrar Ltd", "registration_date": "2003-04-11",
        "expiration_date": "2027-04-11", "nameservers": ["ns1.example-dns.com"] }
    ],
    "dns_records": [
      { "type": "MX", "name": "acmecorp.com", "value": "aspmx.l.google.com", "ttl": 3600, "priority": 1 }
    ],
    "ssl_certificates": [
      { "id": "…", "subject_common_name": "*.acmecorp.com",
        "subject_alternative_names": ["*.acmecorp.com", "acmecorp.com"],
        "issuer": { "common_name": "R11", "organization": "Let's Encrypt" },
        "valid_from": "2026-06-20T00:00:00+00:00", "valid_to": "2026-09-18T00:00:00+00:00",
        "is_expired": false, "is_wildcard": true,
        "key_algorithm": "RSA", "key_size": 2048, "signature_algorithm": "SHA256withRSA",
        "fingerprint_sha256": "9f86d081884c7d65…" }
    ],
    "network_ranges": [
      { "cidr": "203.0.113.0/24", "netname": "ACME-NET", "organization": "Acme Corporation",
        "country_code": "GB", "asn": "AS64500" }
    ],
    "vulnerabilities": [
      {
        "id": "…",
        "title": "TLS 1.0 enabled",
        "severity": "medium",
        "cve_id": null,
        "cwe_id": "CWE-326",
        "cvss_score": null,
        "epss_score": null,
        "kev_status": false,
        "public_exploit_available": false,
        "status": "open",
        "affected_component": "vpn.acmecorp.com:443",
        "description": "The service accepts TLS 1.0 connections…",
        "remediation": "Disable TLS 1.0 and 1.1; require TLS 1.2 or later.",
        "affects": ["vpn.acmecorp.com:443"],
        "instances": [
          { "id": "…", "title": "vpn.acmecorp.com:443", "entity_type": "service",
            "entity_id": "…", "status": "open" }
        ]
      }
    ]
  },
  "meta": {
    "request_id": "8b16efb8-9a6c-4ace-b817-11cbee626d08",
    "api_version": "1.3.0",
    "credits": { "charged": 1, "remaining": 9997 }
  }
}

Response fields

data.scanobjectoptional

Always present, whatever sections you asked for: id, name, status, created_at, the embedded target, and the rollup counts.

data.hostsarrayoptional

One entry per discovered host: hostname, fqdn, os (name/family/version where known), ip_addresses (with geo + ASN/ISP), and open_ports. Each open port carries the service observed on it — service, product, version, ssl, http_status, http_title and cpe strings. There is no separate ports or services collection; this is it.

data.vulnerabilitiesarrayoptional

Ordered worst-first by severity. Each finding carries scoring context (cvss_score, epss_score, kev_status, public_exploit_available), description and remediation text, the affects list, and its concrete instances (what exactly it was observed on).

data.ssl_certificatesarrayoptional

Ordered by expiry (valid_to ascending) — the certificates about to bite come first.

A scan that isn't yours looks exactly like one that doesn't exist

An unknown scan_id — or another organisation's — returns 200 OK with data set to null, and the credit is still charged. The API never distinguishes "absent" from "not yours"; that distinction would itself leak which ids exist. Take scan ids from GET /my/scans rather than guessing.

Exposed credentials are not in this document

Breach-exposed credentials have their own endpoint, GET /my/scans/{scan_id}/credentials, which returns them redacted to existence flags. The bundle's scan.counts.exposed_credentials tells you how many exist.

Errors

StatuserrorWhen
400invalid_scan_id, invalid_sectionsThe path or sections failed validation. Free — rejected before billing.
401invalid_authorizationMissing or malformed Authorization header.
401unauthorizedInvalid/revoked key, IP not allowlisted, or the key lacks the api:org scope.
402insufficient_creditsOrganisation credit balance is below 1.
405method_not_allowedOnly GET and OPTIONS are accepted.
500internalUnexpected server error (credit auto-refunded).

See Errors for the full error reference.