ExonaExona API
API Reference

Retrieve a Scan

Retrieve a scan by ID. Poll this endpoint until status is "completed" or "failed".

Endpoint

GET https://platform.exonalab.com/api/v1/scans/{id}

Path parameters

idstringrequired

The scan ID returned by POST /v1/scans. Example: scn_01hx7m2d3e4f5g6h7j8k9l0mn


Request headers

Authorizationstringrequired

Bearer exo_live_... or Bearer exo_test_...


Response

idstring

The scan ID.

statusstring

"processing" | "completed" | "failed"

created_atstring

ISO 8601 timestamp of when the scan was created.

completed_atstring

ISO 8601 timestamp of when the scan completed. Present only when status is "completed" or "failed".

companyobject

The company the scan was run for.

  • name (string): company name as submitted
  • website (string): company website as submitted
resultobject

The full scan result. Present only when status is "completed". See Scan Result for the complete schema.

errorobject

Present only when status is "failed". Contains code, message, and optionally hint.


Polling

The recommended polling interval is 10 seconds. The response includes a Retry-After header while status is "processing", which suggests how many seconds to wait before the next poll:

Retry-After: 10

Most scans complete in 30–120 seconds. Set a reasonable timeout in your polling loop (e.g. 5 minutes) and handle a persistent "processing" status as a soft error.

import requests
import time
 
def poll_scan(scan_id, api_key, timeout_seconds=300):
    base_url = "https://platform.exonalab.com/api/v1"
    headers = {"Authorization": f"Bearer {api_key}"}
    deadline = time.time() + timeout_seconds
 
    while time.time() < deadline:
        response = requests.get(f"{base_url}/scans/{scan_id}", headers=headers)
        response.raise_for_status()
        data = response.json()
 
        if data["status"] == "completed":
            return data["result"]
        elif data["status"] == "failed":
            raise RuntimeError(
                f"Scan failed [{data['error']['code']}]: {data['error']['message']}"
            )
 
        # Respect the Retry-After header if present, otherwise default to 10s
        retry_after = int(response.headers.get("Retry-After", 10))
        time.sleep(retry_after)
 
    raise TimeoutError(f"Scan {scan_id} did not complete within {timeout_seconds}s")

Examples

import requests
 
response = requests.get(
    "https://platform.exonalab.com/api/v1/scans/scn_01hx7m2d3e4f5g6h7j8k9l0mn",
    headers={"Authorization": "Bearer exo_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"},
)
print(response.json())

Response: Processing

{
  "id": "scn_01hx7m2d3e4f5g6h7j8k9l0mn",
  "status": "processing",
  "created_at": "2026-04-03T09:00:00Z",
  "company": {
    "name": "Acme AI Ltd",
    "website": "https://acme.ai"
  }
}

Response: Completed

{
  "id": "scn_01hx7m2d3e4f5g6h7j8k9l0mn",
  "status": "completed",
  "created_at": "2026-04-03T09:00:00Z",
  "completed_at": "2026-04-03T09:01:12Z",
  "company": {
    "name": "Acme AI Ltd",
    "website": "https://acme.ai"
  },
  "result": {
    "enrichment": { ... },
    "risk_assessment": { ... },
    "matched_incidents": [ ... ],
    "data_freshness": {
      "sources_last_checked": "2026-04-03T09:01:00Z"
    }
  }
}

Response: Failed

{
  "id": "scn_01hx7m2d3e4f5g6h7j8k9l0mn",
  "status": "failed",
  "created_at": "2026-04-03T09:00:00Z",
  "completed_at": "2026-04-03T09:03:00Z",
  "company": {
    "name": "Acme AI Ltd",
    "website": "https://acme.ai"
  },
  "error": {
    "code": "ENRICHMENT_INSUFFICIENT_DATA",
    "message": "Not enough public information was found to generate a reliable risk profile for this company.",
    "hint": "Check that the website URL is correct and publicly accessible. Very new companies or those with minimal web presence may not yield sufficient data.",
    "request_id": "req_abc123xyz"
  }
}

Error codes

CodeHTTPDescription
SCAN_NOT_FOUND404No scan exists with this ID under your API key.
AUTHENTICATION_REQUIRED401No API key provided.
INVALID_API_KEY401The provided key is invalid or revoked.

On this page