Reference

How to call API v1

Base path is /api/v1. Replace YOUR_TENANT_HOST with your tenant hostname. Every mutation is audit-logged. Submissions still require analytical context — bare indicators are rejected.

Authentication

  1. Sign in to your tenant and open My account → Personal API tokens.
  2. Create a token. Copy the secret once — it is shown only at creation.
  3. Send it on every request: Authorization: Bearer pk_live_…

Tokens inherit the caller’s role permissions (User, Admin, or Developer). Service principal secrets use the same header shape when configured by an admin.

Python
import requests

TOKEN = "pk_live_••••••••••••"
headers = {
    "Authorization": f"Bearer {TOKEN}",
    "Content-Type": "application/json",
}

Conventions

  • Pagination — list routes accept limit (1–100, default 50) and an optional page token in the cursor query parameter. Responses wrap rows in { data, pagination }. Pass pagination.nextCursor as the next page token.
  • Soft delete — deleting an indicator deprecates it; deleting a case moves it to cold. Evidence and audit are append-only (no PATCH/DELETE).
  • No bare indicators POST /api/v1/indicators requires type, value, activityClass, sensitivity, and non-empty context.

Licensing & attribution

Verdicts returned by the API are licensed for use inside your own product or workflow (an OEM embed — you surface our verdicts in your app). Two obligations apply and vary by plan — the verdict payload carries attributionRequired and attributionText so you can enforce them programmatically.

  • Attribution required — wherever you surface a verdict to an end user, display the accompanying attributionText (default “Verdict by Intellescope) visibly next to it. The permalink field should remain reachable. Applies to Developer and Build embeds; waived on Volume and OEM.
  • No redistribution rights — you may embed verdicts in your product, but reselling, syndicating, or white-labeling them as a standalone data feed to third parties is not permitted below the OEM tier. The OEM tier — for resellers such as MSSPs (managed security service providers) that surface verdicts under their own brand — grants redistribution and white-label rights under a negotiated agreement.
PlanAttribution requiredRedistribution rights
DeveloperRequiredNot permitted
BuildRequiredNot permitted
VolumeWaivedNot permitted
OEMWaivedGranted

These terms summarize the licensing tiers; the binding terms are in your service agreement.

Errors

Failures return JSON with an error object. Validation failures may include a details array.

HTTP 401
{
  "error": {
    "code": "unauthorized",
    "message": "Invalid or missing API key"
  }
}
HTTP 403
{
  "error": {
    "code": "forbidden",
    "message": "Permission denied"
  }
}
HTTP 404
{
  "error": {
    "code": "not_found",
    "message": "Indicator not found"
  }
}
HTTP 400
{
  "error": {
    "code": "bad_request",
    "message": "Validation failed.",
    "details": [
      {
        "code": "context_required",
        "message": "Analytical context is required with every submission."
      }
    ]
  }
}

Endpoint catalog

  • GET/api/v1/indicators

    List indicators in the tenant inventory (paginated).

    Query: limit (1–100, default 50), cursor (page token)

    Mock request & response
    indicator.read
  • POST/api/v1/indicators

    Submit an indicator with required analytical context.

    Mock request & response
    indicator.submit
  • GET/api/v1/indicators/:id

    Fetch one indicator with attached evidence claims.

    Mock request & response
    indicator.read
  • PATCH/api/v1/indicators/:id

    Update curation fields or transition state (published / deprecated).

    Body: description?, tags?, activityClass?, state?: "published"|"deprecated"

    Mock request & response
    indicator.promote
  • DELETE/api/v1/indicators/:id

    Soft-delete an indicator by deprecating it. Evidence is retained.

    Mock request & response
    indicator.deprecate
  • GET/api/v1/evidence

    List evidence claims (optionally filtered to one indicator).

    Query: limit, cursor (page token), indicatorId. Evidence is append-only — no PATCH/DELETE.

    Mock request & response
    indicator.read
  • GET/api/v1/cases

    List cases.

    Query: limit, cursor (page token)

    Mock request & response
    case.read
  • POST/api/v1/cases

    Create a case.

    Mock request & response
    case.create
  • GET/api/v1/cases/:id

    Fetch one case.

    Mock request & response
    case.read
  • PATCH/api/v1/cases/:id

    Update case assignment / status fields.

    Mock request & response
    case.assign
  • DELETE/api/v1/cases/:id

    Soft-delete a case by moving it to cold. History is retained.

    Mock request & response
    case.dispose
  • GET/api/v1/dispositions

    List closed-loop disposition outcomes.

    Query: limit, cursor (page token), indicatorId

    Mock request & response
    disposition.read
  • GET/api/v1/sources

    List contributing sources and reliability metadata.

    Query: limit, cursor (page token)

    Mock request & response
    indicator.read
  • GET/api/v1/threat-groups

    List threat group records.

    Query: limit, cursor (page token)

    Mock request & response
    indicator.read
  • GET/api/v1/threat-groups/:id

    Fetch one threat group.

    Mock request & response
    indicator.read
  • PATCH/api/v1/threat-groups/:id

    Update threat group metadata (name, aliases, techniques).

    Mock request & response
    indicator.promote
  • GET/api/v1/campaigns

    List campaigns.

    Query: limit, cursor (page token)

    Mock request & response
    indicator.read
  • GET/api/v1/campaigns/:id

    Fetch one campaign.

    Mock request & response
    indicator.read
  • PATCH/api/v1/campaigns/:id

    Update campaign fields.

    Mock request & response
    indicator.promote
  • DELETE/api/v1/campaigns/:id

    Hard-delete a campaign record.

    Mock request & response
    indicator.deprecate
  • GET/api/v1/audit

    List audit events (own events unless admin).

    Query: limit, cursor (page token). Audit is append-only — no PATCH/DELETE.

    Mock request & response
    audit.read_own · audit.read_all
  • GET/api/v1/users/me

    Export the authenticated user's personal data (GDPR Art. 15).

    Mock request & response
    settings.read
  • POST/api/v1/verdicts

    Compute an OEM verdict for one observable (unique-observable meter).

    Headers: Idempotency-Key. Body: type, value, format?=json|stix. Sandbox: pk_test_.

    Mock request & response
    indicator.read
  • GET/api/v1/verdicts/:id

    Retrieve a previously computed OEM verdict.

    Mock request & response
    indicator.read
  • POST/api/v1/verdicts/:id/outcome

    Report resolution outcome — free, unmetered.

    Mock request & response
    indicator.read
  • POST/api/v1/batches

    Submit an async batch of observables (202 + poll).

    Mock request & response
    indicator.read
  • GET/api/v1/batches/:id

    Poll batch job status.

    Mock request & response
    indicator.read
  • GET/api/v1/batches/:id/results

    Retrieve completed batch verdicts.

    Mock request & response
    indicator.read
  • GET/api/v1/usage

    Unique-observable usage for the current billing month.

    Mock request & response
    indicator.read
  • GET/api/v1/openapi

    Published OpenAPI 3.0 document (unauthenticated).

    Mock request & response
    settings.read

Mock requests & responses

Examples use the Python requests library. IDs and timestamps are illustrative — use values from your tenant. Secrets are never returned by the API.

GET/api/v1/indicators

List indicators

Returns a page of indicator summaries. Pass limit (1–100) and an optional page token from the previous page’s pagination.nextCursor.

Python request
import requests

BASE = "https://YOUR_TENANT_HOST"
TOKEN = "pk_live_••••••••••••"

headers = {
    "Authorization": f"Bearer {TOKEN}",
}

response = requests.get(
    f"{BASE}/api/v1/indicators?limit=2",
    headers=headers,
    timeout=30,
)
response.raise_for_status()
data = response.json()
print(data)
Response 200
{
  "data": [
    {
      "id": "ind_033",
      "type": "ip",
      "value": "203.0.113.45",
      "state": "published",
      "sensitivity": "TLP:AMBER",
      "activityClass": "c2",
      "tags": [
        "beacon",
        "finance"
      ],
      "description": "Beaconing from finance subnet",
      "createdAt": "2026-05-14T12:00:00.000Z",
      "updatedAt": "2026-05-20T09:10:00.000Z",
      "evidenceCount": 4
    }
  ],
  "pagination": {
    "limit": 2,
    "nextCursor": "ind_032",
    "hasMore": true
  }
}
POST/api/v1/indicators

Submit an indicator

Creates a provisional indicator. Context is required — bare values are rejected. activityClass and sensitivity must use platform vocabulary.

Python request
import requests

BASE = "https://YOUR_TENANT_HOST"
TOKEN = "pk_live_••••••••••••"

headers = {
    "Authorization": f"Bearer {TOKEN}",
    "Content-Type": "application/json",
}

payload = {
    "type": "ip",
    "value": "203.0.113.50",
    "activityClass": "c2",
    "sensitivity": "TLP:AMBER",
    "context": "Observed beaconing from finance subnet during IR-4412.",
    "tags": [
        "beacon"
    ]
}

response = requests.post(
    f"{BASE}/api/v1/indicators",
    headers=headers,
    json=payload,
    timeout=30,
)
response.raise_for_status()
data = response.json()
print(data)
Response 201
{
  "data": {
    "id": "ind_044",
    "type": "ip",
    "value": "203.0.113.50",
    "state": "provisional",
    "sensitivity": "TLP:AMBER",
    "activityClass": "c2",
    "tags": [
      "beacon"
    ],
    "createdAt": "2026-07-12T16:00:00.000Z",
    "updatedAt": "2026-07-12T16:00:00.000Z",
    "evidenceCount": 1
  }
}
GET/api/v1/indicators/:id

Get indicator detail

Includes attached evidence claims and attribution arrays.

Python request
import requests

BASE = "https://YOUR_TENANT_HOST"
TOKEN = "pk_live_••••••••••••"

headers = {
    "Authorization": f"Bearer {TOKEN}",
}

response = requests.get(
    f"{BASE}/api/v1/indicators/ind_033",
    headers=headers,
    timeout=30,
)
response.raise_for_status()
data = response.json()
print(data)
Response 200
{
  "data": {
    "id": "ind_033",
    "type": "ip",
    "value": "203.0.113.45",
    "state": "published",
    "sensitivity": "TLP:AMBER",
    "activityClass": "c2",
    "tags": [
      "beacon"
    ],
    "createdAt": "2026-05-14T12:00:00.000Z",
    "updatedAt": "2026-05-20T09:10:00.000Z",
    "evidenceCount": 2,
    "threatGroupIds": [
      "tg_apt29"
    ],
    "campaignIds": [],
    "mitreTechniques": [
      "T1071"
    ],
    "internalObservation": true,
    "isMassScanner": false,
    "evidence": [
      {
        "id": "ev_1",
        "claimType": "reputation_assertion",
        "claimSummary": "Malicious reputation from vendor feed",
        "sourceId": "src_virustotal",
        "sourceConfidence": 0.82,
        "observedAt": "2026-05-19T00:00:00.000Z",
        "assertedAt": "2026-05-19T00:05:00.000Z",
        "signal": "supporting"
      }
    ]
  }
}
PATCH/api/v1/indicators/:id

Update or promote / deprecate

Patch curation fields, or set state to published (promote) or deprecated.

Python request
import requests

BASE = "https://YOUR_TENANT_HOST"
TOKEN = "pk_live_••••••••••••"

headers = {
    "Authorization": f"Bearer {TOKEN}",
    "Content-Type": "application/json",
}

payload = {
    "description": "Confirmed C2 after IR-4412",
    "state": "published"
}

response = requests.patch(
    f"{BASE}/api/v1/indicators/ind_044",
    headers=headers,
    json=payload,
    timeout=30,
)
response.raise_for_status()
data = response.json()
print(data)
Response 200
{
  "data": {
    "id": "ind_044",
    "type": "ip",
    "value": "203.0.113.50",
    "state": "published",
    "sensitivity": "TLP:AMBER",
    "activityClass": "c2",
    "tags": [
      "beacon",
      "confirmed"
    ],
    "description": "Confirmed C2 after IR-4412",
    "createdAt": "2026-07-12T16:00:00.000Z",
    "updatedAt": "2026-07-12T16:05:00.000Z",
    "evidenceCount": 1
  }
}
DELETE/api/v1/indicators/:id

Deprecate an indicator

Soft-delete: sets state to deprecated. Evidence is retained for chain of custody.

Python request
import requests

BASE = "https://YOUR_TENANT_HOST"
TOKEN = "pk_live_••••••••••••"

headers = {
    "Authorization": f"Bearer {TOKEN}",
}

response = requests.delete(
    f"{BASE}/api/v1/indicators/ind_044",
    headers=headers,
    timeout=30,
)
response.raise_for_status()
data = response.json()
print(data)
Response 200
{
  "data": {
    "id": "ind_044",
    "type": "ip",
    "value": "203.0.113.50",
    "state": "deprecated",
    "sensitivity": "TLP:AMBER",
    "activityClass": "c2",
    "tags": [
      "beacon"
    ],
    "createdAt": "2026-07-12T16:00:00.000Z",
    "updatedAt": "2026-07-12T17:00:00.000Z",
    "evidenceCount": 1
  },
  "meta": {
    "softDeleted": true,
    "state": "deprecated"
  }
}
GET/api/v1/evidence

List evidence

Evidence is append-only. Filter with indicatorId when you need claims for one IOC.

Python request
import requests

BASE = "https://YOUR_TENANT_HOST"
TOKEN = "pk_live_••••••••••••"

headers = {
    "Authorization": f"Bearer {TOKEN}",
}

response = requests.get(
    f"{BASE}/api/v1/evidence?indicatorId=ind_033&limit=10",
    headers=headers,
    timeout=30,
)
response.raise_for_status()
data = response.json()
print(data)
Response 200
{
  "data": [
    {
      "id": "ev_1",
      "claimType": "human_analyst_assertion",
      "claimSummary": "Observed beaconing from finance subnet during IR-4412.",
      "sourceId": "src_analyst",
      "sourceConfidence": 0.9,
      "observedAt": "2026-07-12T15:55:00.000Z",
      "assertedAt": "2026-07-12T16:00:00.000Z",
      "signal": "supporting"
    }
  ],
  "pagination": {
    "limit": 10,
    "hasMore": false
  }
}
GET/api/v1/cases

List cases

Analytical workspaces for coordinating response.

Python request
import requests

BASE = "https://YOUR_TENANT_HOST"
TOKEN = "pk_live_••••••••••••"

headers = {
    "Authorization": f"Bearer {TOKEN}",
}

response = requests.get(
    f"{BASE}/api/v1/cases?limit=5",
    headers=headers,
    timeout=30,
)
response.raise_for_status()
data = response.json()
print(data)
Response 200
{
  "data": [
    {
      "id": "case_a1",
      "title": "Finance subnet beacon review",
      "status": "open",
      "severity": "elevated",
      "indicatorIds": [
        "ind_033"
      ],
      "assigneeId": "u_priya",
      "createdAt": "2026-07-10T10:00:00.000Z",
      "updatedAt": "2026-07-11T08:00:00.000Z"
    }
  ],
  "pagination": {
    "limit": 5,
    "hasMore": false
  }
}
POST/api/v1/cases

Create a case

title, severity, and summary are required.

Python request
import requests

BASE = "https://YOUR_TENANT_HOST"
TOKEN = "pk_live_••••••••••••"

headers = {
    "Authorization": f"Bearer {TOKEN}",
    "Content-Type": "application/json",
}

payload = {
    "title": "Finance subnet beacon review",
    "severity": "elevated",
    "summary": "Correlating C2 candidates from IR-4412.",
    "indicatorIds": [
        "ind_033"
    ]
}

response = requests.post(
    f"{BASE}/api/v1/cases",
    headers=headers,
    json=payload,
    timeout=30,
)
response.raise_for_status()
data = response.json()
print(data)
Response 201
{
  "data": {
    "id": "case_b2",
    "title": "Finance subnet beacon review",
    "status": "open",
    "severity": "elevated",
    "indicatorIds": [
      "ind_033"
    ],
    "assigneeId": "u_priya",
    "createdAt": "2026-07-12T16:10:00.000Z",
    "updatedAt": "2026-07-12T16:10:00.000Z"
  }
}
GET/api/v1/cases/:id

Get one case

Fetch a single case by id.

Python request
import requests

BASE = "https://YOUR_TENANT_HOST"
TOKEN = "pk_live_••••••••••••"

headers = {
    "Authorization": f"Bearer {TOKEN}",
}

response = requests.get(
    f"{BASE}/api/v1/cases/case_b2",
    headers=headers,
    timeout=30,
)
response.raise_for_status()
data = response.json()
print(data)
Response 200
{
  "data": {
    "id": "case_b2",
    "title": "Finance subnet beacon review",
    "status": "open",
    "severity": "elevated",
    "indicatorIds": [
      "ind_033"
    ],
    "assigneeId": "u_priya",
    "createdAt": "2026-07-12T16:10:00.000Z",
    "updatedAt": "2026-07-12T16:10:00.000Z"
  }
}
PATCH/api/v1/cases/:id

Update a case

Change assignment, severity, summary, indicators, or status. Resolving statuses require resolutionNote.

Python request
import requests

BASE = "https://YOUR_TENANT_HOST"
TOKEN = "pk_live_••••••••••••"

headers = {
    "Authorization": f"Bearer {TOKEN}",
    "Content-Type": "application/json",
}

payload = {
    "status": "in_progress",
    "assignedTo": "u_alex"
}

response = requests.patch(
    f"{BASE}/api/v1/cases/case_b2",
    headers=headers,
    json=payload,
    timeout=30,
)
response.raise_for_status()
data = response.json()
print(data)
Response 200
{
  "data": {
    "id": "case_b2",
    "title": "Finance subnet beacon review",
    "status": "in_progress",
    "severity": "elevated",
    "indicatorIds": [
      "ind_033"
    ],
    "assigneeId": "u_alex",
    "createdAt": "2026-07-12T16:10:00.000Z",
    "updatedAt": "2026-07-12T16:20:00.000Z"
  }
}
DELETE/api/v1/cases/:id

Close a case (cold)

Soft-delete: moves the case to cold. History remains.

Python request
import requests

BASE = "https://YOUR_TENANT_HOST"
TOKEN = "pk_live_••••••••••••"

headers = {
    "Authorization": f"Bearer {TOKEN}",
}

response = requests.delete(
    f"{BASE}/api/v1/cases/case_b2",
    headers=headers,
    timeout=30,
)
response.raise_for_status()
data = response.json()
print(data)
Response 200
{
  "data": {
    "id": "case_b2",
    "title": "Finance subnet beacon review",
    "status": "cold",
    "severity": "elevated",
    "indicatorIds": [
      "ind_033"
    ],
    "assigneeId": "u_alex",
    "createdAt": "2026-07-12T16:10:00.000Z",
    "updatedAt": "2026-07-12T18:00:00.000Z"
  },
  "meta": {
    "softDeleted": true,
    "status": "cold"
  }
}
GET/api/v1/dispositions

List dispositions

Closed-loop outcomes when an indicator met your controls.

Python request
import requests

BASE = "https://YOUR_TENANT_HOST"
TOKEN = "pk_live_••••••••••••"

headers = {
    "Authorization": f"Bearer {TOKEN}",
}

response = requests.get(
    f"{BASE}/api/v1/dispositions?indicatorId=ind_033",
    headers=headers,
    timeout=30,
)
response.raise_for_status()
data = response.json()
print(data)
Response 200
{
  "data": [
    {
      "id": "disp_1",
      "indicatorId": "ind_033",
      "controlId": "ctrl_edr",
      "controlKind": "edr",
      "deployedAt": "2026-05-15T00:00:00.000Z",
      "outcome": "blocked",
      "analystConfirmed": true,
      "encounterCount": 12,
      "blockCount": 12,
      "lastEncounterAt": "2026-05-20T08:00:00.000Z"
    }
  ],
  "pagination": {
    "limit": 50,
    "hasMore": false
  }
}
GET/api/v1/sources

List sources

Contributing sources and reliability metadata for your tenant.

Python request
import requests

BASE = "https://YOUR_TENANT_HOST"
TOKEN = "pk_live_••••••••••••"

headers = {
    "Authorization": f"Bearer {TOKEN}",
}

response = requests.get(
    f"{BASE}/api/v1/sources",
    headers=headers,
    timeout=30,
)
response.raise_for_status()
data = response.json()
print(data)
Response 200
{
  "data": [
    {
      "id": "src_greynoise",
      "name": "GreyNoise",
      "category": "vendor",
      "reliability": 0.94,
      "independentDimension": "internet_scanning",
      "description": "Internet-wide background noise classification."
    }
  ],
  "pagination": {
    "limit": 50,
    "hasMore": false
  }
}
GET/api/v1/threat-groups

List threat groups

Canonical actor records with aliases.

Python request
import requests

BASE = "https://YOUR_TENANT_HOST"
TOKEN = "pk_live_••••••••••••"

headers = {
    "Authorization": f"Bearer {TOKEN}",
}

response = requests.get(
    f"{BASE}/api/v1/threat-groups",
    headers=headers,
    timeout=30,
)
response.raise_for_status()
data = response.json()
print(data)
Response 200
{
  "data": [
    {
      "id": "tg_apt29",
      "canonicalName": "APT29",
      "aliases": [
        "Cozy Bear",
        "Midnight Blizzard"
      ],
      "motivation": "espionage",
      "origin": "RU",
      "active": true,
      "attackTechniques": [
        "T1071",
        "T1566"
      ]
    }
  ],
  "pagination": {
    "limit": 50,
    "hasMore": false
  }
}
GET/api/v1/threat-groups/:id

Get one threat group

Fetch a single threat group by id.

Python request
import requests

BASE = "https://YOUR_TENANT_HOST"
TOKEN = "pk_live_••••••••••••"

headers = {
    "Authorization": f"Bearer {TOKEN}",
}

response = requests.get(
    f"{BASE}/api/v1/threat-groups/tg_apt29",
    headers=headers,
    timeout=30,
)
response.raise_for_status()
data = response.json()
print(data)
Response 200
{
  "data": {
    "id": "tg_apt29",
    "canonicalName": "APT29",
    "aliases": [
      "Cozy Bear",
      "Midnight Blizzard"
    ],
    "motivation": "espionage",
    "origin": "RU",
    "active": true,
    "attackTechniques": [
      "T1071",
      "T1566"
    ]
  }
}
PATCH/api/v1/threat-groups/:id

Update a threat group

Patch name, aliases, motivation, origin, active, or ATT&CK techniques.

Python request
import requests

BASE = "https://YOUR_TENANT_HOST"
TOKEN = "pk_live_••••••••••••"

headers = {
    "Authorization": f"Bearer {TOKEN}",
    "Content-Type": "application/json",
}

payload = {
    "aliases": [
        "Cozy Bear",
        "Midnight Blizzard",
        "The Dukes"
    ],
    "justification": "Alias update from sector advisory"
}

response = requests.patch(
    f"{BASE}/api/v1/threat-groups/tg_apt29",
    headers=headers,
    json=payload,
    timeout=30,
)
response.raise_for_status()
data = response.json()
print(data)
Response 200
{
  "data": {
    "id": "tg_apt29",
    "canonicalName": "APT29",
    "aliases": [
      "Cozy Bear",
      "Midnight Blizzard",
      "The Dukes"
    ],
    "motivation": "espionage",
    "origin": "RU",
    "active": true,
    "attackTechniques": [
      "T1071",
      "T1566"
    ]
  }
}
GET/api/v1/campaigns

List campaigns

Campaign timeline records linked to threat groups.

Python request
import requests

BASE = "https://YOUR_TENANT_HOST"
TOKEN = "pk_live_••••••••••••"

headers = {
    "Authorization": f"Bearer {TOKEN}",
}

response = requests.get(
    f"{BASE}/api/v1/campaigns",
    headers=headers,
    timeout=30,
)
response.raise_for_status()
data = response.json()
print(data)
Response 200
{
  "data": [
    {
      "id": "camp_1",
      "name": "Winter phishing wave",
      "threatGroupId": "tg_apt29",
      "startedAt": "2026-01-01T00:00:00.000Z",
      "summary": "Credential harvest against finance users."
    }
  ],
  "pagination": {
    "limit": 50,
    "hasMore": false
  }
}
GET/api/v1/campaigns/:id

Get one campaign

Fetch a single campaign by id.

Python request
import requests

BASE = "https://YOUR_TENANT_HOST"
TOKEN = "pk_live_••••••••••••"

headers = {
    "Authorization": f"Bearer {TOKEN}",
}

response = requests.get(
    f"{BASE}/api/v1/campaigns/camp_1",
    headers=headers,
    timeout=30,
)
response.raise_for_status()
data = response.json()
print(data)
Response 200
{
  "data": {
    "id": "camp_1",
    "name": "Winter phishing wave",
    "threatGroupId": "tg_apt29",
    "startedAt": "2026-01-01T00:00:00.000Z",
    "summary": "Credential harvest against finance users."
  }
}
PATCH/api/v1/campaigns/:id

Update a campaign

Update name, summary, dates, or threatGroupId.

Python request
import requests

BASE = "https://YOUR_TENANT_HOST"
TOKEN = "pk_live_••••••••••••"

headers = {
    "Authorization": f"Bearer {TOKEN}",
    "Content-Type": "application/json",
}

payload = {
    "summary": "Credential harvest — closed after mailbox resets.",
    "endedAt": "2026-02-01T00:00:00.000Z"
}

response = requests.patch(
    f"{BASE}/api/v1/campaigns/camp_1",
    headers=headers,
    json=payload,
    timeout=30,
)
response.raise_for_status()
data = response.json()
print(data)
Response 200
{
  "data": {
    "id": "camp_1",
    "name": "Winter phishing wave",
    "threatGroupId": "tg_apt29",
    "startedAt": "2026-01-01T00:00:00.000Z",
    "endedAt": "2026-02-01T00:00:00.000Z",
    "summary": "Credential harvest — closed after mailbox resets."
  }
}
DELETE/api/v1/campaigns/:id

Delete a campaign

Removes the campaign record. Prefer ending the campaign when history matters.

Python request
import requests

BASE = "https://YOUR_TENANT_HOST"
TOKEN = "pk_live_••••••••••••"

headers = {
    "Authorization": f"Bearer {TOKEN}",
}

response = requests.delete(
    f"{BASE}/api/v1/campaigns/camp_1",
    headers=headers,
    timeout=30,
)
response.raise_for_status()
data = response.json()
print(data)
Response 200
{
  "data": {
    "id": "camp_1"
  },
  "meta": {
    "deleted": true
  }
}
GET/api/v1/audit

List audit events

Immutable activity log. Non-admins receive their own events; admins can read the full tenant log.

Python request
import requests

BASE = "https://YOUR_TENANT_HOST"
TOKEN = "pk_live_••••••••••••"

headers = {
    "Authorization": f"Bearer {TOKEN}",
}

response = requests.get(
    f"{BASE}/api/v1/audit?limit=5",
    headers=headers,
    timeout=30,
)
response.raise_for_status()
data = response.json()
print(data)
Response 200
{
  "data": [
    {
      "id": "aud_1",
      "at": "2026-07-12T16:05:00.000Z",
      "actorId": "u_priya",
      "actorKind": "user",
      "action": "indicator.promote",
      "resourceKind": "indicator",
      "resourceId": "ind_044"
    }
  ],
  "pagination": {
    "limit": 5,
    "hasMore": true,
    "nextCursor": "aud_0"
  }
}
GET/api/v1/users/me

Export my data

GDPR Art. 15 export of the authenticated user’s profile and API key metadata (secrets never included).

Python request
import requests

BASE = "https://YOUR_TENANT_HOST"
TOKEN = "pk_live_••••••••••••"

headers = {
    "Authorization": f"Bearer {TOKEN}",
}

response = requests.get(
    f"{BASE}/api/v1/users/me",
    headers=headers,
    timeout=30,
)
response.raise_for_status()
data = response.json()
print(data)
Response 200
{
  "exportedAt": "2026-07-12T16:30:00.000Z",
  "personalData": {
    "id": "u_priya",
    "email": "priya.iyer@example.com",
    "fullName": "Priya Iyer",
    "role": "user",
    "positionTitle": "Senior Threat Intelligence Analyst"
  },
  "apiKeys": [
    {
      "id": "pak_1",
      "name": "local triage script",
      "prefix": "pk_live_…3d58",
      "status": "active",
      "createdAt": "2026-07-01T00:00:00.000Z"
    }
  ]
}
POST/api/v1/verdicts

Compute a verdict

Computes an OEM verdict for one observable and bills a unique-observable if new this month. Send Idempotency-Key to make retries safe. Sandbox keys (pk_test_) return synthetic verdicts and never bill. Use format: "stix" for a STIX 2.1 Opinion note.

Python request
import requests

BASE = "https://YOUR_TENANT_HOST"
TOKEN = "pk_live_••••••••••••"

headers = {
    "Authorization": f"Bearer {TOKEN}",
    "Content-Type": "application/json",
}

payload = {
    "type": "ip",
    "value": "45.148.10.141"
}

response = requests.post(
    f"{BASE}/api/v1/verdicts",
    headers=headers,
    json=payload,
    timeout=30,
)
response.raise_for_status()
data = response.json()
print(data)
Response 200
{
  "data": {
    "id": "ver_9f2c17a4",
    "observable": {
      "type": "ip",
      "value": "45.148.10.141"
    },
    "verdict": {
      "score": 696,
      "bucket": "high",
      "confidence": 0.86,
      "uncertaintyLow": 640,
      "uncertaintyHigh": 742,
      "completeness": 0.9
    },
    "hypotheses": [],
    "evidence": [
      {
        "claimType": "reputation_assertion",
        "claimSummary": "210k abuse reports — SSH brute-force, all blocked",
        "signal": "supporting",
        "sourceId": "src_abuseipdb",
        "sourceName": "AbuseIPDB",
        "observedAt": "2026-07-25T22:00:00.000Z",
        "retrievedAt": "2026-07-26T12:00:00.000Z"
      }
    ],
    "engineVersion": "v0.3.1",
    "hypothesisLibraryVersion": "hyp-lib-2026.07",
    "computedAt": "2026-07-26T12:00:00.000Z",
    "cacheAge": 0,
    "permalink": "https://YOUR_TENANT_HOST/v/ver_9f2c17a4",
    "attributionRequired": true,
    "attributionText": "Verdict by Intellescope"
  }
}
GET/api/v1/verdicts/:id

Retrieve a verdict

Fetches a previously computed verdict by id. Re-reads are free — they do not consume unique-observable quota.

Python request
import requests

BASE = "https://YOUR_TENANT_HOST"
TOKEN = "pk_live_••••••••••••"

headers = {
    "Authorization": f"Bearer {TOKEN}",
}

response = requests.get(
    f"{BASE}/api/v1/verdicts/ver_9f2c17a4",
    headers=headers,
    timeout=30,
)
response.raise_for_status()
data = response.json()
print(data)
Response 200
{
  "data": {
    "id": "ver_9f2c17a4",
    "observable": {
      "type": "ip",
      "value": "45.148.10.141"
    },
    "verdict": {
      "score": 696,
      "bucket": "high",
      "confidence": 0.86,
      "uncertaintyLow": 640,
      "uncertaintyHigh": 742,
      "completeness": 0.9
    },
    "hypotheses": [],
    "evidence": [
      {
        "claimType": "reputation_assertion",
        "claimSummary": "210k abuse reports — SSH brute-force, all blocked",
        "signal": "supporting",
        "sourceId": "src_abuseipdb",
        "sourceName": "AbuseIPDB",
        "observedAt": "2026-07-25T22:00:00.000Z",
        "retrievedAt": "2026-07-26T12:00:00.000Z"
      }
    ],
    "engineVersion": "v0.3.1",
    "hypothesisLibraryVersion": "hyp-lib-2026.07",
    "computedAt": "2026-07-26T12:00:00.000Z",
    "cacheAge": 0,
    "permalink": "https://YOUR_TENANT_HOST/v/ver_9f2c17a4",
    "attributionRequired": true,
    "attributionText": "Verdict by Intellescope"
  }
}
POST/api/v1/verdicts/:id/outcome

Report an outcome

Report how the verdict resolved in your environment. Free and unmetered — this closed-loop signal improves calibration.

Python request
import requests

BASE = "https://YOUR_TENANT_HOST"
TOKEN = "pk_live_••••••••••••"

headers = {
    "Authorization": f"Bearer {TOKEN}",
    "Content-Type": "application/json",
}

payload = {
    "outcome": "malicious",
    "note": "Blocked at the mail gateway."
}

response = requests.post(
    f"{BASE}/api/v1/verdicts/ver_9f2c17a4/outcome",
    headers=headers,
    json=payload,
    timeout=30,
)
response.raise_for_status()
data = response.json()
print(data)
Response 200
{
  "data": {
    "verdictId": "ver_9f2c17a4",
    "outcome": "malicious",
    "note": "Blocked at the mail gateway.",
    "reportedAt": "2026-07-26T13:00:00.000Z",
    "reporterTenantId": "tnt_acme"
  },
  "metered": false,
  "message": "Outcome recorded. This endpoint is free and does not consume unique-observable quota."
}
POST/api/v1/batches

Submit a batch

Queues an async batch of observables and returns 202 with a poll URL. Each unique observable in the batch meters the same as a single verdict.

Python request
import requests

BASE = "https://YOUR_TENANT_HOST"
TOKEN = "pk_live_••••••••••••"

headers = {
    "Authorization": f"Bearer {TOKEN}",
    "Content-Type": "application/json",
}

payload = {
    "items": [
        {
            "type": "ip",
            "value": "45.148.10.141"
        },
        {
            "type": "domain",
            "value": "malware-c2.example"
        }
    ]
}

response = requests.post(
    f"{BASE}/api/v1/batches",
    headers=headers,
    json=payload,
    timeout=30,
)
response.raise_for_status()
data = response.json()
print(data)
Response 202
{
  "data": {
    "id": "batch_7c3d10f2",
    "status": "queued",
    "createdAt": "2026-07-26T13:05:00.000Z",
    "itemCount": 2,
    "poll": "/api/v1/batches/batch_7c3d10f2",
    "results": "/api/v1/batches/batch_7c3d10f2/results"
  }
}
GET/api/v1/batches/:id

Poll batch status

Poll for job status. A worker that stalls for 15 minutes is reaped to failed so a job never hangs forever.

Python request
import requests

BASE = "https://YOUR_TENANT_HOST"
TOKEN = "pk_live_••••••••••••"

headers = {
    "Authorization": f"Bearer {TOKEN}",
}

response = requests.get(
    f"{BASE}/api/v1/batches/batch_7c3d10f2",
    headers=headers,
    timeout=30,
)
response.raise_for_status()
data = response.json()
print(data)
Response 200
{
  "data": {
    "id": "batch_7c3d10f2",
    "status": "completed",
    "createdAt": "2026-07-26T13:05:00.000Z",
    "updatedAt": "2026-07-26T13:05:12.000Z",
    "completedAt": "2026-07-26T13:05:12.000Z",
    "itemCount": 2,
    "resultCount": 2,
    "error": null
  }
}
GET/api/v1/batches/:id/results

Retrieve batch results

Returns the completed verdict products once status is completed.

Python request
import requests

BASE = "https://YOUR_TENANT_HOST"
TOKEN = "pk_live_••••••••••••"

headers = {
    "Authorization": f"Bearer {TOKEN}",
}

response = requests.get(
    f"{BASE}/api/v1/batches/batch_7c3d10f2/results",
    headers=headers,
    timeout=30,
)
response.raise_for_status()
data = response.json()
print(data)
Response 200
{
  "data": [
    {
      "id": "ver_9f2c17a4",
      "observable": {
        "type": "ip",
        "value": "45.148.10.141"
      },
      "verdict": {
        "score": 696,
        "bucket": "high",
        "confidence": 0.86,
        "uncertaintyLow": 640,
        "uncertaintyHigh": 742,
        "completeness": 0.9
      },
      "hypotheses": [],
      "evidence": [
        {
          "claimType": "reputation_assertion",
          "claimSummary": "210k abuse reports — SSH brute-force, all blocked",
          "signal": "supporting",
          "sourceId": "src_abuseipdb",
          "sourceName": "AbuseIPDB",
          "observedAt": "2026-07-25T22:00:00.000Z",
          "retrievedAt": "2026-07-26T12:00:00.000Z"
        }
      ],
      "engineVersion": "v0.3.1",
      "hypothesisLibraryVersion": "hyp-lib-2026.07",
      "computedAt": "2026-07-26T12:00:00.000Z",
      "cacheAge": 0,
      "permalink": "https://YOUR_TENANT_HOST/v/ver_9f2c17a4",
      "attributionRequired": true,
      "attributionText": "Verdict by Intellescope"
    },
    {
      "id": "ver_4b8e02d1",
      "observable": {
        "type": "domain",
        "value": "malware-c2.example"
      },
      "verdict": {
        "score": 696,
        "bucket": "high",
        "confidence": 0.86,
        "uncertaintyLow": 640,
        "uncertaintyHigh": 742,
        "completeness": 0.9
      },
      "hypotheses": [],
      "evidence": [
        {
          "claimType": "reputation_assertion",
          "claimSummary": "210k abuse reports — SSH brute-force, all blocked",
          "signal": "supporting",
          "sourceId": "src_abuseipdb",
          "sourceName": "AbuseIPDB",
          "observedAt": "2026-07-25T22:00:00.000Z",
          "retrievedAt": "2026-07-26T12:00:00.000Z"
        }
      ],
      "engineVersion": "v0.3.1",
      "hypothesisLibraryVersion": "hyp-lib-2026.07",
      "computedAt": "2026-07-26T12:00:00.000Z",
      "cacheAge": 0,
      "permalink": "https://YOUR_TENANT_HOST/v/ver_4b8e02d1",
      "attributionRequired": true,
      "attributionText": "Verdict by Intellescope"
    }
  ]
}
GET/api/v1/usage

Check usage

Unique-observable usage for the current billing month. Soft-capped — you are never hard-blocked; overage bills per unit above the pool.

Python request
import requests

BASE = "https://YOUR_TENANT_HOST"
TOKEN = "pk_live_••••••••••••"

headers = {
    "Authorization": f"Bearer {TOKEN}",
}

response = requests.get(
    f"{BASE}/api/v1/usage",
    headers=headers,
    timeout=30,
)
response.raise_for_status()
data = response.json()
print(data)
Response 200
{
  "data": {
    "plan": "build",
    "periodStart": "2026-07-01T00:00:00.000Z",
    "periodEnd": "2026-08-01T00:00:00.000Z",
    "uniqueUsed": 6482,
    "uniqueLimit": 10000,
    "uniqueRemaining": 3518,
    "overageUnits": 0,
    "overageUsdPerUnit": 0.06,
    "softCapped": true,
    "alertAt80": false,
    "alertAt100": false,
    "features": {
      "webhooks": true,
      "sla": null,
      "attributionRequired": true,
      "redistributionRights": false
    }
  }
}
GET/api/v1/openapi

Fetch the OpenAPI document

Published OpenAPI 3.0 spec for the whole v1 surface. Public — the Authorization header is optional here.

Python request
import requests

BASE = "https://YOUR_TENANT_HOST"
TOKEN = "pk_live_••••••••••••"

headers = {
    "Authorization": f"Bearer {TOKEN}",
}

response = requests.get(
    f"{BASE}/api/v1/openapi",
    headers=headers,
    timeout=30,
)
response.raise_for_status()
data = response.json()
print(data)
Response 200
{
  "openapi": "3.0.3",
  "info": {
    "title": "Intellescope API",
    "version": "v1"
  },
  "paths": {
    "/api/v1/verdicts": {
      "post": {
        "summary": "Compute an OEM verdict"
      }
    }
  }
}