# How to Use the SkillSpector Python API for Programmatic Security Scanning

> Learn how to use the SkillSpector Python API programmatically. Invoke the LangGraph workflow with a state dictionary to get security findings, reports, and risk scores.

- Repository: [NVIDIA Corporation/SkillSpector](https://github.com/NVIDIA/SkillSpector)
- Tags: how-to-guide
- Published: 2026-06-20

---

**SkillSpector exposes a compiled LangGraph workflow via `skillspector.graph` that you invoke with a state dictionary containing `skill_path`, `output_format`, and `use_llm` to return findings, reports, and risk scores.**

NVIDIA/SkillSpector provides a programmatic interface built on LangGraph that allows you to embed security scanning directly into Python applications. Instead of shelling out to the command line, you can import the compiled `graph` object and invoke it with a typed state dictionary. This guide explains how to use the SkillSpector Python API to analyze skills programmatically, based on the actual implementation in the NVIDIA/SkillSpector repository.

## Understanding the SkillSpector Architecture

### LangGraph Workflow

The core of the SkillSpector Python API is a compiled LangGraph workflow defined in [`src/skillspector/graph.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/graph.py). The `graph` object (exposed via `skillspector.__init__`) is a standard LangChain runnable that chains together nodes for input handling, context building, static analyzers, meta-analysis, and report generation.

### State Schema

All data passing between nodes adheres to `SkillspectorState`, a **TypedDict** defined in [`src/skillspector/state.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/state.py). This schema dictates the required and optional fields for scan execution, ensuring type safety across the workflow.

### CLI Reference Implementation

The command-line interface in [`src/skillspector/cli.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/cli.py) serves as a thin wrapper that demonstrates the programmatic pattern. It prepares the initial state dictionary and calls `graph.invoke()`, making it the definitive reference for embedding the scanner in your own code.

## Importing the Graph and Building State

To execute a scan programmatically, import the compiled graph from `skillspector.graph` and construct a state dictionary matching the `SkillspectorState` schema. Required keys include `skill_path` (local path or remote URL), `output_format` (terminal, json, markdown, or sarif), and `use_llm` (boolean).

```python
from pathlib import Path
from skillspector.graph import graph

# Build the minimal state required for a scan

state = {
    "skill_path": str(Path("./my_skill").resolve()),
    "output_format": "terminal",
    "use_llm": True,
    # Optional: specify custom YARA rules directory

    # "yara_rules_dir": "/path/to/custom/rules",

}

# Optional: add tracing configuration for observability

trace_cfg = {
    "run_name": "my-skill-scan",
    "tags": ["skillspector", "ci-cd"],
    "metadata": {"skill_path": state["skill_path"]},
}

# Execute the workflow

result = graph.invoke(state, config=trace_cfg)

# Access results

print(result.get("report_body", ""))
for finding in result.get("findings", []):
    print(f"- [{finding.severity}] {finding.title}")

```

The `graph.invoke(state, config=...)` method call mirrors the CLI implementation in [`src/skillspector/cli.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/cli.py), executing the full pipeline and returning a plain dictionary containing the final fields.

## Configuring LLM Providers

The SkillSpector Python API supports multiple LLM providers through environment variables or explicit state configuration. The provider protocol is defined in [`src/skillspector/providers/base.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/providers/base.py), with resolution handled by the internal `providers.registry`.

Set the provider and model via environment variables before invocation:

```python
import os
from skillspector.graph import graph

os.environ["SKILLSPECTOR_PROVIDER"] = "openai"
os.environ["SKILLSPECTOR_MODEL"] = "gpt-4o-mini"

state = {
    "skill_path": "https://github.com/example/malicious-skill",
    "output_format": "json",
    "use_llm": True,
}

result = graph.invoke(state)
print(result["report_body"])

```

Alternatively, you can pass a `model_config` entry in the state dictionary to override environment settings for specific invocations.

## Running Static-Only Scans

You can disable LLM-based analysis for faster execution or air-gapped environments by setting `use_llm: False`. This skips the `meta_analyzer` and all semantic analyzers, leaving only static YARA and pattern checks.

```python
from skillspector.graph import graph

state = {
    "skill_path": "./some_skill",
    "output_format": "sarif",
    "use_llm": False,  # Disable LLM nodes

}

result = graph.invoke(state)

# SARIF JSON is available directly in the result

sarif_output = result["sarif_report"]
print(sarif_output)

```

This programmatic behavior mirrors the `--no-llm` CLI flag handled in [`src/skillspector/cli.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/cli.py).

## Accessing Scan Results

The dictionary returned by `graph.invoke()` contains structured data defined in [`src/skillspector/models.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/models.py). Key output fields include:

- **`findings`** – A list of `Finding` objects containing analyzer details, location information, severity levels, and remediation suggestions.
- **`report_body`** – Human-readable report text formatted according to your `output_format` specification.
- **`sarif_report`** – Static Analysis Results Interchange Format (SARIF) JSON for integration with security dashboards.
- **`risk_score`** – Aggregated numeric risk metric calculated by the meta-analysis node.

Because the graph is a standard LangChain runnable, you can also pass a custom `RunnableConfig` for tracing, metadata logging, or callback handling during invocation.

## Summary

- Import the compiled **`graph`** from `skillspector.graph` to access the LangGraph workflow constructed in [`src/skillspector/graph.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/graph.py).
- Construct a state dictionary matching **`SkillspectorState`** with required keys: `skill_path`, `output_format`, and `use_llm`.
- Invoke the scan via **`graph.invoke(state, config=...)`** to execute the full pipeline from input handling to reporting.
- Configure LLM providers via **`SKILLSPECTOR_PROVIDER`** and **`SKILLSPECTOR_MODEL`** environment variables, or pass credentials through the state’s `model_config` field.
- Access structured results via **`findings`**, **`sarif_report`**, and **`risk_score`** fields in the returned dictionary, with data models defined in [`src/skillspector/models.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/models.py).

## Frequently Asked Questions

### What is the difference between `create_graph` and the `graph` object?

`create_graph` is a factory function exposed in `skillspector.__init__` that constructs a fresh `StateGraph` instance, while `graph` is the pre-compiled, ready-to-use runnable that the package exports for immediate invocation. Use `graph` for standard programmatic access; use `create_graph` only if you need to modify the node topology before compilation.

### How do I configure authentication for private LLM providers?

Authentication is handled via standard provider-specific environment variables (e.g., `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`) or by passing a `model_config` dictionary in the state with credential fields. The provider protocol in [`src/skillspector/providers/base.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/providers/base.py) defines the interface for credential resolution, allowing the registry to instantiate the correct `ChatModelProvider` with your credentials.

### Can I run SkillSpector without an LLM for faster static analysis?

Yes. Set `use_llm: False` in your state dictionary before calling `graph.invoke()`. This bypasses the LLM-dependent meta-analyzer and semantic analyzers, executing only the static YARA and pattern-matching nodes. This is equivalent to the `--no-llm` CLI flag and significantly reduces execution time.

### What format does the `findings` field return?

The `findings` field contains a list of `Finding` objects as defined in [`src/skillspector/models.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/models.py). Each object includes attributes such as `analyzer` (string identifying the detection rule), `location` (file path and line numbers), `severity` (categorical risk level), `title` (short description), and `description` (detailed explanation with remediation guidance). These objects are JSON-serializable and can be directly ingested by security orchestration platforms.