Threat intelligence API / integrations

OpenAI

OpenAI models can query ThreatCluster 2 ways: function calling against the REST API from your own code, and a custom GPT in ChatGPT that imports our OpenAPI spec as an Action. Both give the model live threat clusters, IOCs, CVE records and ransomware leak-site victims.

Prerequisites

  1. A ThreatCluster account (Free works) and an API key from Settings → API. The key is sent in the X-API-Key header.
  2. Base URL: https://threatcluster.io/api/public/v1
  3. An OpenAI API key (for function calling) or a ChatGPT account that can create GPTs (for Actions).

A free ThreatCluster key gets 100 credits per day, 30 requests per minute, a 7-day lookback window, and the read scopes threats:read, iocs:read, entities:read, vulns:read and darkweb:read.

Setup: function calling with the Responses API

The function below is derived from the live OpenAPI spec for GET /search, which takes q, limit, days and include_articles. One search call covers clusters, entities and the dark web, so most agents need only this tool plus plain GETs on the detail endpoints it points to.

  1. Define the tool and a function that executes it. Returning the budget headers alongside the data lets the model see what the call cost and how much budget is left:
    import json, os, requests
    from openai import OpenAI
    
    client = OpenAI()
    
    tools = [{
        "type": "function",
        "name": "search_threatcluster",
        "description": "Search ThreatCluster threat clusters, entities and "
                       "dark-web records in one call. Chain the returned "
                       "short_id into /threats/{short_id} detail endpoints.",
        "parameters": {
            "type": "object",
            "properties": {
                "q": {"type": "string", "description": "A CVE id, threat actor, malware family, product or company name"},
                "limit": {"type": "integer", "description": "Max results, default 10"},
                "days": {"type": "integer", "description": "Only look at the last N days"},
                "include_articles": {"type": "boolean", "description": "Include source articles, default false"}
            },
            "required": ["q"],
            "additionalProperties": False
        }
    }]
    
    def search_threatcluster(q, limit=10, days=None, include_articles=False):
        params = {"q": q, "limit": limit, "include_articles": include_articles}
        if days is not None:
            params["days"] = days
        r = requests.get(
            "https://threatcluster.io/api/public/v1/search",
            params=params,
            headers={"X-API-Key": os.environ["TC_API_KEY"]},
            timeout=30,
        )
        r.raise_for_status()
        return {
            "cost": r.headers.get("X-Request-Cost"),
            "credits_remaining_today": r.headers.get("X-RateLimit-Remaining"),
            "data": r.json(),
        }
  2. Send a request with the tool attached:
    input_list = [{"role": "user", "content": "What do we know about CVE-2026-46037?"}]
    
    response = client.responses.create(
        model="gpt-5.6",  # any current model with tool calling
        tools=tools,
        input=input_list,
    )
  3. Execute any function_call items the model returns, append the outputs, and call again:
    input_list += response.output
    for item in response.output:
        if item.type == "function_call":
            result = search_threatcluster(**json.loads(item.arguments))
            input_list.append({
                "type": "function_call_output",
                "call_id": item.call_id,
                "output": json.dumps(result),
            })
    
    response = client.responses.create(model="gpt-5.6", tools=tools, input=input_list)
    print(response.output_text)

Setup: a custom GPT with Actions

  1. In ChatGPT, create a GPT and open the Actions section of the editor.
  2. Choose Import from URL and point it at https://threatcluster.io/api/public/v1/openapi.json. It is an OpenAPI 3.1.0 document, the version the GPT editor expects.
  3. Set authentication to API key and paste the key from Settings → API. The spec declares the header it belongs in (X-API-Key).
  4. Test an action from the editor preview before publishing. Note the operation names the import produces and refer to them in the GPT's instructions.

The spec imports every endpoint, including write endpoints your key's scopes will refuse. A 403 response names the missing scope. If you want a smaller action surface, paste a trimmed copy of the spec instead of importing the full URL.

Worked example

A real captured request and response. Searching a company name finds dark-web leak-site records even when there is no news cluster:

GET /search?q=Intraco
{
  "query": "Intraco",
  "clusters": [],
  "entities": [],
  "darkweb": [
    {
      "type": "victim",
      "name": "PT Intraco Penta Tbk",
      "id": "bdd665d27f502e10",
      "date": "2026-09-01T15:53:54.238628+00:00",
      "group": "direwolf",
      "country": "ID",
      "sector": "Manufacturing"
    }
  ],
  "limit": 10,
  "total": 1
}

Chain the victim id into /darkweb/ransomware/victim/bdd665d27f502e10 for the enriched record, or a cluster's short_id into /threats/{short_id}, /threats/{short_id}/iocs and /threats/{short_id}/stix.

Budgets for agents

Free keys spend a daily budget of 100 credits. Most requests cost 1 credit, /search costs 5, IOC and STIX pulls cost 3, and a dark-web victim enrichment costs 10. Every response carries X-Request-Cost, and budgeted tiers also get X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. The function above passes those headers back to the model so it can pace itself. An exhausted budget returns 429 daily_budget_exceeded with a Retry-After header; the correct behavior is to stop and retry after that many seconds. A request that finds nothing refunds its credits and returns X-Request-Cost: 0, so speculative lookups are cheap.

Full endpoint reference: /api/public/v1/docs. CLI and agent keys: /cli. Plans and credits: /pricing.