Skip to main content

Platform Documentation

Learn how to use ClusterHawk for IP clustering and threat detection

Search Documentation

1
Submit IPs

Upload your IP addresses of interest through our secure interface. Our platform handles datasets up to 5000 addresses.

2
Analysis

Our deterministic ensemble pipeline analyzes patterns, identifies relationships, and generates threat intelligence automatically — same input, same clusters, same reasoning, every run.

3
Receive reports

Get comprehensive threat intelligence reports with IOCs, YARA rules, and hunting queries.

4
Execute hunting queries

Use our automated hunting query execution service to validate findings and monitor for new threats.

User Guide

API Usage Guide


API Usage Guide

ClusterHawk provides a public API that allows you to programmatically submit prediction jobs and retrieve results using your trained models. This guide covers everything you need to know to integrate ClusterHawk into your security workflows.

Prerequisites

Before using the API, you need:

  • Team tier or higher subscription - API access is not available on Analyst plans
  • At least one trained model - You must have completed a training job to create models for predictions
  • An active API key - Generate one from your Profile page (shown for 30 seconds only)
Generating an API Key

To generate an API key:

  1. Navigate to your Profile page
  2. Scroll to the "API Access" section
  3. Click "Generate API Key" (requires Team tier or higher)
  4. Copy your API key immediately - it's only shown for 30 seconds
  5. Store the key securely in your application's configuration
Authentication

All API requests must include your API key for authentication. You can provide the API key in two ways:

curl -X POST https://clusterhawk.chawkr.com/api/v1/public/predict \
  -H "X-API-Key: chawkr_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"model_name": "my-model", "ip_addresses": ["1.2.3.4", "5.6.7.8"]}'
Method 2: Authorization Header
curl -X POST https://clusterhawk.chawkr.com/api/v1/public/predict \
  -H "Authorization: Bearer chawkr_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"model_name": "my-model", "ip_addresses": ["1.2.3.4", "5.6.7.8"]}'
Available Endpoints
Submit Prediction Job

POST /api/v1/public/predict

Submit a new prediction job using one of your trained models.

Request Body:

{
  "model_name": "network-classification-v1",
  "ip_addresses": [
    "192.168.1.100",
    "10.0.0.50",
    "203.0.113.42"
  ],
  "job_name": "Weekly Threat Assessment"
}

Response (Success):

{
  "success": true,
  "job_id": "job_abc123def456",
  "message": "Prediction job created successfully using model 'network-classification-v1'",
  "model_name": "network-classification-v1",
  "ip_count": 3
}
Check Job Status

GET /api/v1/public/jobs/{job_id}/status

Returns the job's current status, progress percentage, and status message.

Example Request:

curl -X GET https://clusterhawk.chawkr.com/api/v1/public/jobs/job_abc123def456/status \
  -H "X-API-Key: chawkr_your_api_key_here"

Response:

{
  "job_id": "job_abc123def456",
  "status": "completed",
  "progress": 100.0,
  "status_message": "Prediction pipeline completed successfully",
  "started_at": "2024-01-15T10:25:00Z",
  "completed_at": "2024-01-15T10:28:45Z",
  "api_request": true,
  "model_name": "network-classification-v1"
}
Get Prediction Results

GET /api/v1/public/jobs/{job_id}/results

Retrieve the prediction results once the job is completed. For user-trained models, each prediction includes a label field with the actor label from the training job.

Each prediction row carries the contract's trust gate in the kind field. Read it first. confident_match rows are the actionable subset. ambiguous_diffuse and ambiguous_split rows hedge across multiple candidates; iterate the candidates array as a unit rather than relying on top-1. out_of_distribution rows do not match any trained pattern. predicted_cluster and confidence are intentionally null on those rows to prevent silent false positives in SIEM joins. top1_minus_top2 and effective_n are diagnostic fields that quantify the gap between the leading candidates and the entropy-effective number of candidates respectively.

Response (User-Trained Model):

{
  "success": true,
  "job_id": "job_abc123def456",
  "pipeline_type": "REGULAR_MODEL_PREDICTION",
  "results": {
    "prediction": {
      "predictions": [
        {
          "ip": "192.168.1.100",
          "predicted_cluster": 2,
          "confidence": 0.94,
          "kind": "confident_match",
          "top1_minus_top2": 0.83,
          "effective_n": 1.21,
          "candidates": [
            { "cluster_id": 2, "confidence": 0.94 }
          ],
          "label": "['Web Crawler']"
        },
        {
          "ip": "10.0.0.50",
          "predicted_cluster": 1,
          "confidence": 0.42,
          "kind": "ambiguous_split",
          "top1_minus_top2": 0.05,
          "effective_n": 2.41,
          "candidates": [
            { "cluster_id": 1, "confidence": 0.42 },
            { "cluster_id": 7, "confidence": 0.37 },
            { "cluster_id": 3, "confidence": 0.11 }
          ],
          "label": "['Scanner']"
        },
        {
          "ip": "203.0.113.42",
          "predicted_cluster": null,
          "confidence": null,
          "kind": "out_of_distribution",
          "top1_minus_top2": null,
          "effective_n": 8.74,
          "candidates": [],
          "label": null
        }
      ],
      "total_predictions": 3,
      "model_info": {
        "model_id": "job_abc123def456"
      }
    }
  },
  "created_at": "2024-01-15T10:25:00Z",
  "completed_at": "2024-01-15T10:28:45Z",
  "api_request": true,
  "model_name": "network-classification-v1"
}

Response (Prebuilt Model - Enterprise Only):

{
  "success": true,
  "job_id": "job_xyz789abc123",
  "pipeline_type": "ADVANCED_MODEL_PREDICTION",
  "results": {
    "prediction": {
      "predictions": [
        {
          "ip": "203.0.113.42",
          "predicted_cluster": 11,
          "confidence": 0.89,
          "kind": "confident_match",
          "top1_minus_top2": 0.71,
          "effective_n": 1.34,
          "candidates": [
            { "cluster_id": 11, "confidence": 0.89 },
            { "cluster_id": 4, "confidence": 0.07 }
          ],
          "primary_characteristic": "APAC residential telecom: CHINANET / Bharti Airtel SOHO routers",
          "key_indicators": "Dropbear SSH 2020.81, Mosquitto MQTT 1.6, JA3 e7d705a3286e19ea42f587b344ee6865"
        },
        {
          "ip": "198.51.100.7",
          "predicted_cluster": null,
          "confidence": null,
          "kind": "out_of_distribution",
          "top1_minus_top2": null,
          "effective_n": 12.4,
          "candidates": []
        }
      ],
      "total_predictions": 2
    }
  },
  "created_at": "2024-01-15T10:25:00Z",
  "completed_at": "2024-01-15T10:28:45Z",
  "api_request": true,
  "model_name": "CHAWKR_STORM_0940_BRUTEFORCE"
}
List Available Models

GET /api/v1/public/models

List all available models for the authenticated user, including both user-trained models and prebuilt models (Enterprise tier only).

Example Request:

curl -X GET https://clusterhawk.chawkr.com/api/v1/public/models \
  -H "X-API-Key: chawkr_your_api_key_here"

Response:

{
  "success": true,
  "models": [
    {
      "model_name": "network-classification-v1",
      "job_id": "job_abc123def456",
      "pipeline_type": "REGULAR_MODEL_TRAINING",
      "created_at": "2024-01-10T14:30:00Z",
      "completed_at": "2024-01-10T14:45:00Z",
      "training_ip_count": 1000,
      "description": "Model trained with REGULAR_MODEL_TRAINING pipeline",
      "is_prebuilt": false
    },
    {
      "model_name": "CHAWKR_BOTNET_DETECTOR",
      "job_id": null,
      "pipeline_type": "PREBUILT",
      "description": "Prebuilt CHAWKR model: CHAWKR_BOTNET_DETECTOR",
      "is_prebuilt": true
    }
  ],
  "total_count": 2,
  "prebuilt_count": 1,
  "user_model_count": 1
}
Check Concurrent Job Quota

GET /api/v1/public/quota/concurrent-jobs

Check your current concurrent job quota and availability before submitting new jobs.

Example Request:

curl -X GET https://clusterhawk.chawkr.com/api/v1/public/quota/concurrent-jobs \
  -H "X-API-Key: chawkr_your_api_key_here"

Response:

{
  "success": true,
  "quota": {
    "current_usage": 1,
    "max_concurrent": 2,
    "available": 1,
    "can_submit": true,
    "tier": "professional",
    "utilization_percentage": 50.0
  },
  "active_jobs": {
    "count": 1,
    "jobs": [
      {
        "job_id": "job_xyz789abc123",
        "status": "running",
        "pipeline_type": "REGULAR_MODEL_PREDICTION",
        "created_at": "2024-01-15T10:20:00Z",
        "ip_count": 150
      }
    ]
  },
  "api_request": true
}
Error Handling

The API uses standard HTTP status codes and returns structured error responses:

Authentication Error (401):

{
  "success": false,
  "status": "error",
  "error": {
    "code": "401",
    "message": "API key required. Provide via X-API-Key header or Authorization header with 'Bearer chawkr_...' format",
    "details": {}
  }
}

Permission Error (403):

{
  "success": false,
  "status": "error",
  "error": {
    "code": "403",
    "message": "Prediction API requires Hobby tier or higher subscription",
    "details": {}
  }
}

Model Not Found (404):

{
  "success": false,
  "status": "error",
  "error": {
    "code": "404",
    "message": "Model 'invalid-model-name' not found in your account",
    "details": {}
  }
}
Rate Limiting and Quotas

API usage is subject to your subscription tier limits:

  • Team: 200 predictions per month
  • Professional: 500 predictions per month
  • Enterprise: 1,000 predictions per month

Rate limiting is enforced to ensure fair usage. If you exceed limits, you'll receive a 429 status code:

{
  "success": false,
  "status": "error",
  "error": {
    "code": "429",
    "message": "Rate limit exceeded. Please try again later.",
    "details": {
      "retry_after": 60
    }
  }
}
Best Practices
  • Secure API Key Storage: Store your API key securely using environment variables or secure configuration management. Never commit API keys to version control.
  • Error Handling: Implement proper error handling for all API responses, including authentication, rate limiting, and server errors.
  • Polling for Results: When checking job status, implement exponential backoff to avoid excessive polling. Start with 5-second intervals and increase gradually.
  • Batch Processing: Submit multiple IP addresses in a single prediction job rather than making individual requests for each IP.
  • Monitor Quotas: Track your prediction usage through the Dashboard to avoid hitting monthly limits unexpectedly.
  • Model Management: Use descriptive model names that clearly indicate their purpose and training data characteristics.
Python Example Integration

Here's a complete Python example for integrating with the ClusterHawk API:

import requests
import time
import os

class ClusterHawkAPI:
    def __init__(self, api_key, base_url="https://clusterhawk.chawkr.com"):
        self.api_key = api_key
        self.base_url = base_url
        self.headers = {
            "X-API-Key": api_key,
            "Content-Type": "application/json"
        }

    def submit_prediction(self, model_name, ip_addresses, job_name=None):
        """Submit a prediction job."""
        payload = {
            "model_name": model_name,
            "ip_addresses": ip_addresses,
            "job_name": job_name or f"Prediction-{int(time.time())}"
        }

        response = requests.post(
            f"{self.base_url}/api/v1/public/predict",
            headers=self.headers,
            json=payload
        )

        if response.status_code == 200:
            return response.json()
        else:
            raise Exception(f"API Error: {response.status_code} - {response.text}")

    def get_job_status(self, job_id):
        """Check job status."""
        response = requests.get(
            f"{self.base_url}/api/v1/public/jobs/{job_id}/status",
            headers=self.headers
        )

        if response.status_code == 200:
            return response.json()
        else:
            raise Exception(f"API Error: {response.status_code} - {response.text}")

    def get_results(self, job_id):
        """Get prediction results."""
        response = requests.get(
            f"{self.base_url}/api/v1/public/jobs/{job_id}/results",
            headers=self.headers
        )

        if response.status_code == 200:
            return response.json()
        else:
            raise Exception(f"API Error: {response.status_code} - {response.text}")

    def list_models(self):
        """List available models."""
        response = requests.get(
            f"{self.base_url}/api/v1/public/models",
            headers=self.headers
        )

        if response.status_code == 200:
            return response.json()
        else:
            raise Exception(f"API Error: {response.status_code} - {response.text}")

    def check_quota(self):
        """Check concurrent job quota availability."""
        response = requests.get(
            f"{self.base_url}/api/v1/public/quota/concurrent-jobs",
            headers=self.headers
        )

        if response.status_code == 200:
            return response.json()
        else:
            raise Exception(f"API Error: {response.status_code} - {response.text}")

    def predict_and_wait(self, model_name, ip_addresses, job_name=None, max_wait=300):
        """Submit prediction and wait for completion."""
        # Submit job
        job_info = self.submit_prediction(model_name, ip_addresses, job_name)
        job_id = job_info["job_id"]
        print(f"Job submitted: {job_id}")

        # Wait for completion
        start_time = time.time()
        while time.time() - start_time < max_wait:
            status = self.get_job_status(job_id)
            print(f"Job status: {status['status']}")

            if status["status"] == "completed":
                return self.get_results(job_id)
            elif status["status"] in ["failed", "cancelled"]:
                raise Exception(f"Job {status['status']}")

            time.sleep(10)  # Wait 10 seconds before next check

        raise Exception("Job timeout")

# Usage example
def main():
    api_key = os.getenv("CHAWKR_API_KEY")
    if not api_key:
        raise Exception("Please set CHAWKR_API_KEY environment variable")

    client = ClusterHawkAPI(api_key)

    # Check quota before submitting
    quota_info = client.check_quota()
    print(f"Quota Status: {quota_info['quota']['current_usage']}/{quota_info['quota']['max_concurrent']} jobs used")
    print(f"Available slots: {quota_info['quota']['available']}")

    if not quota_info['quota']['can_submit']:
        print("Cannot submit job - quota limit reached")
        return

    # List available models first
    models = client.list_models()
    print(f"Available models: {[m['model_name'] for m in models['models']]}")

    # Predict using your trained model
    results = client.predict_and_wait(
        model_name="network-classification-v1",
        ip_addresses=["192.168.1.100", "10.0.0.50", "203.0.113.42"],
        job_name="Security Assessment"
    )

    # Process results
    prediction_data = results['results']['prediction']
    print(f"Analyzed {prediction_data['total_predictions']} IP addresses")

    for prediction in prediction_data["predictions"]:
        ip = prediction["ip"]
        kind = prediction.get("kind", "unknown")
        cluster = prediction.get("predicted_cluster")
        confidence = prediction.get("confidence")
        candidates = prediction.get("candidates", [])

        # Read the contract's kind field first — it is the trust gate.
        # OOD rows have predicted_cluster=null; act on behavioral evidence,
        # not on the cluster attribution.
        if kind == "out_of_distribution":
            print(f"IP {ip}: out-of-distribution (no fingerprint match)")
        elif kind == "confident_match":
            print(f"IP {ip}: Cluster {cluster} (confident, confidence={confidence:.2f})")
        else:
            # ambiguous_diffuse / ambiguous_split — inspect the candidate set
            cand_str = ", ".join(
                f"{c['cluster_id']}:{c['confidence']:.2f}" for c in candidates
            )
            print(f"IP {ip}: Cluster {cluster} ({kind}, candidates=[{cand_str}])")

if __name__ == "__main__":
    main()
Support and Troubleshooting

If you encounter issues with the API:

  • Check your subscription tier - API access requires Team tier or higher
  • Verify your API key - Ensure it's correctly formatted and not expired
  • Confirm model availability - The model name must match exactly (case-sensitive)
  • Monitor quota usage - Check the Dashboard for remaining prediction quota
  • Contact support - Reach out to [email protected] for assistance