Crimson Crawler Python SDK
Typed, async threat intelligence for Python.
Documentation · PyPI · GitHub · Get an API key
Turn a CVE into a complete, typed attack briefing with one async call. The client also covers package, IOC, CWE, and ATT&CK enrichment, artifact and inventory workflows, grounded reports, and every v1 endpoint without hand-written request models.
Install
pip install crimson-crawler-client
# or
uv add crimson-crawler-client
Python 3.11+.
PyPI publication is pending. The install command above applies once the first release lands; until then, use the repository checkout for evaluation. The badge above will show the registry version once that release succeeds.
Authentication
Set your key in the environment:
export CRIMSON_CRAWLER_API_KEY=your-api-key
Or pass it directly (preferred for tests and multi-tenant code):
async with CrawlerClient(api_key="your-api-key") as client:
...
The constructor argument wins over the env var. Generate a key at crimsoncrawler.com/dashboard.
Custom or local endpoint
The hosted API at https://crimsoncrawler.com is the default. Point the
official package at a local or self-hosted deployment with either form:
export CRIMSON_CRAWLER_BASE_URL=http://localhost:8000
client = CrawlerClient(base_url="http://localhost:8000")
The SDK sends its apikey header to the selected origin. Use a local or
development key when overriding the endpoint; do not reuse a production key
with an origin you do not control. Custom non-loopback endpoints must use
HTTPS. Plain HTTP is accepted only for loopback development URLs such as the
example above. TLS verification stays enabled by default; trust the
deployment's CA when possible. verify_ssl=False or
CRIMSON_CRAWLER_VERIFY_SSL=false is only for isolated local development with
a self-signed certificate. The SDK emits a warning for either override.
Quick start
import asyncio
from crimson_crawler_client import CrawlerClient
async def main() -> None:
async with CrawlerClient() as client:
result = await client.enrich_cve_full("CVE-2024-3400")
if result.cve:
print(result.cve.cvss_score)
for technique in result.techniques or []:
print(technique.technique_id, technique.name)
asyncio.run(main())
Every list-valued field on an enrichment response can be None if that layer wasn't requested or had no data. The or [] guard is the standard pattern.
The client
Constructor
CrawlerClient(
api_key: str | None = None,
base_url: str | None = None,
verify_ssl: bool | None = None,
max_retries: int = 2,
)
| Parameter | Type | Default | Description |
|---|---|---|---|
api_key |
str | None |
env CRIMSON_CRAWLER_API_KEY |
Sent as the apikey header on every request. |
base_url |
str | None |
env CRIMSON_CRAWLER_BASE_URL, then https://crimsoncrawler.com |
Custom, self-hosted, or local API origin. |
verify_ssl |
bool | None |
env CRIMSON_CRAWLER_VERIFY_SSL, then True |
Disable only for isolated local development; prefer trusting the deployment CA. |
max_retries |
int |
2 |
Number of automatic retries on transient failures (see Retry & rate limits). 0 disables. |
Raises MissingCredentials if neither api_key nor the env var is set. Raises ValueError for a malformed or insecure non-loopback base_url, or if max_retries < 0.
Properties
api— pass this to any endpoint function when calling endpoints directly (see Beyond the convenience surface).base_url— read-only; the resolved hosted, custom, or local endpoint.max_retries— read-only; the configured retry budget (see Retry & rate limits).
Async lifecycle
CrawlerClient is an async context manager. The recommended pattern:
async with CrawlerClient() as client:
result = await client.enrich_cve_full("CVE-2024-3400")
Entering opens the HTTP client. Exiting closes it.
If you need finer control:
client = CrawlerClient()
try:
result = await client.enrich_cve_full("CVE-2024-3400")
finally:
await client.aclose()
A single CrawlerClient can issue many concurrent requests. TCP connections are reused across calls, so don't construct one per request.
Convenience methods
Every /v1 operation has a method — 33 of them, plus the search_techniques shortcut. The seven documented in full below cover the highest-traffic patterns; the rest are grouped after them with their signatures. The 15 enrich_*_full methods call their endpoint with a full-walk include=[...] preset baked in (pass include= to override); the others take no preset. Every method is async, returns a typed response model, and raises UnexpectedStatus on a non-2xx.
| Group | Methods |
|---|---|
| Full-walk enrichment (15) | enrich_cve_full · enrich_ioc_full · enrich_cwe_full · enrich_technique_full · enrich_product_full · enrich_package_full · enrich_capec_full · enrich_group_full · enrich_software_full · enrich_campaign_full · enrich_atlas_full · enrich_disarm_full · enrich_defend_full · enrich_location_full · enrich_sector_full |
| Enrichment, no preset (2) | enrich_batch · enrich_poc_source |
| Search (8 + 1) | search_cve · search_cti · search_kev · search_misp · search_knowledgebase · search_vendor · search_poc · search_d3fend · search_techniques |
| Assessment (4) | assess_technique_coverage · assess_vulnerability_exposure · assess_group_exposure · assess_ioc_portfolio |
| Async engines (4) | artifact_enrich · artifact_enrich_status · inventory_enrich · inventory_enrich_status |
enrich_cve_full(cve_id, *, include=None)
Returns CVE details, EPSS exploit probability, KEV status, mapped weaknesses, attack patterns, ATT&CK techniques, TIE threat predictions, MISP events, knowledge-base context, and web findings. Single request.
async with CrawlerClient() as client:
result = await client.enrich_cve_full("CVE-2024-3400")
if result.cve:
print(f"CVSS: {result.cve.cvss_score} ({result.cve.cvss_severity})")
print(f"Description: {result.cve.description}")
if result.epss:
print(f"EPSS: {result.epss.epss} (percentile {result.epss.percentile})")
if result.kev and result.kev.in_kev:
print(f"KEV: added {result.kev.date_added}")
print(f"Weaknesses: {len(result.weaknesses or [])} CWE(s)")
print(f"Attack patterns: {len(result.attack_patterns or [])} CAPEC(s)")
for t in result.techniques or []:
print(f" Technique: {t.technique_id} - {t.name}")
for p in result.predictions or []:
print(f" TIE prediction: {p.technique_id} ({p.probability})")
| Parameter | Type | Default | Description |
|---|---|---|---|
cve_id |
str |
required | CVE identifier, e.g. "CVE-2024-3400". |
include |
list[str] | None |
presets.CVE_FULL_WALK |
Override the default include list. |
Returns: EnrichCVEResponse.
enrich_ioc_full(value, ioc_type="ip-dst", *, include=None)
Returns MISP attribute matches, linked ATT&CK techniques, attributed threat groups, knowledge-base context, and web findings for an indicator (IP, hash, domain, URL).
async with CrawlerClient() as client:
result = await client.enrich_ioc_full("203.0.113.5", "ip-dst")
for attr in result.misp_attributes or []:
print(f"MISP event: {attr.event_info}")
for t in result.techniques or []:
print(f"Linked technique: {t.technique_id}")
| Parameter | Type | Default | Description |
|---|---|---|---|
value |
str |
required | Indicator value. |
ioc_type |
str |
"ip-dst" |
MISP-style attribute type: ip-dst, ip-src, domain, hostname, md5, sha1, sha256, url, etc. |
include |
list[str] | None |
presets.IOC_FULL_WALK |
Override the default include list. |
Returns: EnrichIOCResponse.
enrich_cwe_full(cwe_id, *, include=None)
Returns the weakness, mapped CAPEC attack patterns, ATT&CK techniques, and TIE predictions. Use it when you start from a weakness ID.
async with CrawlerClient() as client:
result = await client.enrich_cwe_full("CWE-79")
if result.weakness:
print(f"Weakness: {result.weakness.name}")
print(f"CAPEC patterns: {len(result.attack_patterns or [])}")
print(f"Techniques: {len(result.techniques or [])}")
| Parameter | Type | Default | Description |
|---|---|---|---|
cwe_id |
str |
required | CWE identifier, e.g. "CWE-79". |
include |
list[str] | None |
presets.CWE_FULL_WALK |
Override the default include list. |
Returns: EnrichCWEResponse.
enrich_technique_full(technique_ids, *, framework="enterprise", include=None)
Returns technique details, TIE predictions, attributed groups, software, campaigns, and a Navigator JSON layer for one or more ATT&CK techniques.
async with CrawlerClient() as client:
result = await client.enrich_technique_full(
["T1190", "T1059.001"],
framework="enterprise",
)
for t in result.techniques or []:
print(f"{t.technique_id}: {t.name}")
for g in result.groups or []:
print(f"Group using these: {g.name}")
| Parameter | Type | Default | Description |
|---|---|---|---|
technique_ids |
list[str] |
required | ATT&CK technique IDs. |
framework |
str |
"enterprise" |
ATT&CK matrix variant: enterprise, ics, or mobile. |
include |
list[str] | None |
presets.TECHNIQUE_FULL |
Override the default include list. |
Returns: EnrichTechniqueResponse.
enrich_product_full(product, *, version=None, vendor=None, include=None)
Finds vulnerabilities affecting a product/version and enriches each matched CVE with severity, exploit signals, weakness mapping, ATT&CK techniques, threat predictions, MISP events, and web context.
async with CrawlerClient() as client:
result = await client.enrich_product_full(
"Apache HTTP Server",
version="2.4.51",
vendor="apache",
)
for cve in result.cves or []:
print(f"{cve.cve_id}: {cve.cvss_score} ({cve.cvss_severity})")
if cve.kev and cve.kev.in_kev:
print(" in KEV")
| Parameter | Type | Default | Description |
|---|---|---|---|
product |
str |
required | Product name, e.g. "Apache HTTP Server", "nginx". |
version |
str | None |
None |
Specific version (e.g. "2.4.51"). Optional. |
vendor |
str | None |
None |
Vendor name to narrow results (e.g. "apache"). Optional. |
include |
list[str] | None |
presets.PRODUCT_FULL |
Override the default include list. |
Returns: EnrichProductResponse.
enrich_package_full(ecosystem, package, *, version=None, include=None)
The package-ecosystem sibling of enrich_product_full. OSV.dev supplies the package's advisories (npm / PyPI / Go / Maven / crates.io …, including the non-CVE GHSA / PYSEC / RUSTSEC / GO findings the CPE-keyed product endpoint can't reach), and the CVEs they alias are enriched through the same CWE → CAPEC → ATT&CK + EPSS/KEV chain.
async with CrawlerClient() as client:
result = await client.enrich_package_full("PyPI", "django", version="4.0")
for cve in result.cves or []:
print(f"{cve.cve_id}: {cve.cvss_score} ({cve.cvss_severity})")
if cve.kev and cve.kev.in_kev:
print(" in KEV")
| Parameter | Type | Default | Description |
|---|---|---|---|
ecosystem |
str |
required | OSV ecosystem, e.g. "PyPI", "npm", "Go", "Maven", "crates.io". |
package |
str |
required | Package name within the ecosystem, e.g. "django". |
version |
str | None |
None |
Specific version (e.g. "4.0"). Optional. |
include |
list[str] | None |
presets.PACKAGE_FULL |
Override the default include list. |
Returns: EnrichPackageResponse.
search_techniques(keyword, *, limit=20)
Keyword-searches the ATT&CK knowledge base and returns matching techniques (attack-patterns).
async with CrawlerClient() as client:
result = await client.search_techniques("credential dumping", limit=10)
for hit in result.results or []:
print(hit)
| Parameter | Type | Default | Description |
|---|---|---|---|
keyword |
str |
required | Free-text query, e.g. "phishing", "credential dumping". |
limit |
int |
20 |
Maximum number of results (1-100). |
Returns: SearchCTIResponse.
The other nine full-walk enrichments
Same shape as the six above: a full-walk preset baked in, include= to override. Each takes an optional id positional or search= — pass one, not both — so you can go straight to an id or find the entity by name.
async with CrawlerClient() as client:
apt29 = await client.enrich_group_full("G0016")
same = await client.enrich_group_full(search="cozy bear")
ml = await client.enrich_atlas_full("AML.T0043")
| Method | Id parameter | Extra keywords | Returns |
|---|---|---|---|
enrich_capec_full(capec_id=None, …) |
CAPEC-66 |
search, include |
EnrichCAPECResponse |
enrich_group_full(group_id=None, …) |
G0016 |
search, framework, include |
EnrichGroupResponse |
enrich_software_full(software_id=None, …) |
S0154 |
search, framework, include |
EnrichSoftwareResponse |
enrich_campaign_full(campaign_id=None, …) |
C0028 |
search, framework, include |
EnrichCampaignResponse |
enrich_atlas_full(technique_id=None, …) |
AML.T0043 |
search, include |
EnrichATLASResponse |
enrich_disarm_full(technique_id=None, …) |
T0001 |
search, include |
EnrichDISARMResponse |
enrich_defend_full(d3fend_id=None, …) |
D3-AL |
search, include |
EnrichD3FENDResponse |
enrich_location_full(location_id=None, …) |
L0001 |
search, include |
EnrichLocationResponse |
enrich_sector_full(sector_id=None, …) |
financial-services |
search, include |
EnrichSectorResponse |
framework is "enterprise" (default), "ics", or "mobile", and only exists where the endpoint supports a matrix variant.
enrich_batch(items)
Up to 50 heterogeneous enrich/* lookups in one request — one call against your rate limit. Items are plain dicts: a type discriminator plus that type's fields and its own optional include. No preset; the list passes through untouched.
async with CrawlerClient() as client:
resp = await client.enrich_batch([
{"type": "cve", "cve_id": "CVE-2024-3400", "include": ["details", "kev"]},
{"type": "ioc", "value": "1.2.3.4", "value_type": "ip-dst"},
{"type": "technique", "technique_id": "T1190"},
])
for item in resp.results or []: # input order preserved
print(item.index, item.type, item.status)
| Parameter | Type | Default | Description |
|---|---|---|---|
items |
list[dict[str, Any]] |
required | 1–50 items. type is one of cve, product, package, cwe, capec, technique, ioc, group, software, campaign, atlas, disarm, location, sector, defend, poc_source. |
Returns: EnrichBatchResponse. One failing item does not fail the batch — each result carries its own status and errors[]. Two field names to get right: IOC items use value_type for the indicator type (type is taken by the discriminator, unlike enrich_ioc_full's ioc_type argument), and the D3FEND discriminator is defend, matching the endpoint path.
enrich_poc_source(repo_url)
LLM-summarized analysis of a proof-of-concept exploit repository — what the code does, how weaponized it looks, what it targets. No include.
async with CrawlerClient() as client:
pocs = await client.search_poc("CVE-2024-3400")
analysis = await client.enrich_poc_source("https://github.com/example/CVE-2024-3400-poc")
Returns: FetchPoCSourceResponse.
Search (8 methods)
One method per search endpoint. search_cve is the only one that accepts include=; none of them uses a preset. search_kev and search_misp take no positional at all — call them bare to browse.
async with CrawlerClient() as client:
weaponized = await client.search_cve(kev_only=True, epss_min=0.9, limit=20)
kev = await client.search_kev(ransomware_status="Known")
answer = await client.search_knowledgebase("how do I detect kerberoasting", alpha=0.6)
| Method | Signature | Returns |
|---|---|---|
search_cve |
(query=None, *, cve_id, stix_id, cwe_id, capec_id, attack_id, cpe, cpes_not_vulnerable, created_by_ref, created_min, created_max, modified_min, modified_max, cvss_min, cvss_v2_min, cvss_v4_min, epss_min, epss_percentile_min, kev_only=False, vuln_status, sort, limit=50, page=1, include) |
SearchCVEResponse |
search_cti |
(query, *, sources=None, types=None, limit=20, page=1, deprecated=None, revoked=None) |
SearchCTIResponse |
search_kev |
(*, cve_id=None, ransomware_status=None, limit=50, page=1) |
SearchKEVResponse |
search_misp |
(*, value, type_attribute, category, event_id, eventinfo, tags, date_from, date_to, published, to_ids, threat_level_id, limit=50, page=1) |
SearchMISPResponse |
search_knowledgebase |
(query, *, collections=None, alpha=0.7, limit=10) |
SearchKnowledgebaseResponse |
search_vendor |
(vendor, *, limit=50) |
SearchVendorResponse |
search_poc |
(cve_id, *, limit=25) |
SearchPoCResponse |
search_d3fend |
(query, *, d3fend_form=None, limit=20, page=1) |
SearchD3FENDResponse |
Constrained values: vuln_status is Analyzed / Awaiting Analysis / Modified / Received / Rejected; sort is created_ascending / created_descending / modified_ascending / modified_descending / epss_score_descending / x_opencti_cvss_base_score_descending; ransomware_status is Known / Unknown; d3fend_form is tactic / mitigation / sub-mitigation / artifact; collections draws from main / large / user / red_team; types from attack-pattern / intrusion-set / malware / tool / campaign / weakness / course-of-action. You pass plain strings — the wrapper converts them.
Assessment (4 methods)
Portfolio questions instead of single-entity lookups: what a set of techniques, CVEs, actors, or indicators means together. Each takes an optional include= passthrough (no preset — omit it and the server default applies); the endpoints cap their inputs at 50.
async with CrawlerClient() as client:
gaps = await client.assess_technique_coverage(["T1190", "T1059.001", "T1566"])
exposure = await client.assess_vulnerability_exposure(
["CVE-2024-3400", "CVE-2021-44228"],
stakeholder={"exposure": "controlled", "mission_prevalence": "essential"},
)
actors = await client.assess_group_exposure(group_ids=["G0016", "G0007"])
portfolio = await client.assess_ioc_portfolio([
{"value": "1.2.3.4", "type": "ip-dst"},
{"value": "evil.example", "type": "domain"},
])
| Method | Signature | Returns |
|---|---|---|
assess_technique_coverage |
(technique_ids, *, framework="enterprise", include=None) |
AssessTechniqueCoverageResponse |
assess_vulnerability_exposure |
(cve_ids, *, stakeholder=None, include=None) |
AssessVulnerabilityExposureResponse |
assess_group_exposure |
(*, group_ids=None, search=None, framework="enterprise", include=None) |
AssessGroupExposureResponse |
assess_ioc_portfolio |
(indicators, *, include=None) |
AssessIOCPortfolioResponse |
stakeholder is the SSVC decision dict (method, exposure, mission_prevalence, human_impact, public_wellbeing_impact) and applies when include asks for ssvc. assess_group_exposure takes group_ids, free-text search terms, or both.
Async engines (4 methods)
The raw submit/status pairs behind the two background enrichment engines. A submit returns a job handle immediately (it counts toward your usage); the status read is usage-exempt, so poll as often as you like. For a one-call version that submits, polls, and hands back the finished report, use client.enricher.enrich or client.inventory_manager.ingest_scan.
async with CrawlerClient() as client:
job = await client.artifact_enrich(text="Suspicious login from 10.1.1.1, then CVE-2024-1234 scan")
status = await client.artifact_enrich_status(job.enrichment_id)
inv = await client.inventory_enrich([{"product": "nginx", "version": "1.24.0"}])
findings = await client.inventory_enrich_status(inv.enrichment_id)
| Method | Signature | Returns |
|---|---|---|
artifact_enrich |
(*, text=None, files=None, formats=None, stakeholder=None, include=None) |
EnrichmentSubmitResponse |
artifact_enrich_status |
(enrichment_id) |
ArtifactEnrichmentJobResponse |
inventory_enrich |
(items, *, include=None, attachments_text=None, attachments_files=None) |
EnrichmentSubmitResponse |
inventory_enrich_status |
(enrichment_id) |
InventoryEnrichmentJobResponse |
files and attachments_files are [{"filename": …, "content_b64": …}]; items are asset dicts (product is the only required key). formats picks the machine exports to build alongside the report (report, json, stix, vex, csaf, navigator, kev_remediation, oscal_poam, misp, csv). A submit returns 202 with status: "queued" plus a queue_position when your key already has 3 enrichments running; past 20 in flight it raises UnexpectedStatus(429) with error: "enrichment_queue_full".
Presets
A preset is a named list[str]: a curated set of include values for one of the common workflows. The point is so you don't have to memorize which include values are valid for which endpoint, or pick the right depth for your use case every time.
The presets module exports 18 — one full-walk default per enrichment endpoint that accepts include, plus three depth variants for the two highest-traffic ones:
| Preset | Use for | Include values |
|---|---|---|
CVE_FULL_WALK |
Default for enrich_cve_full. Full enrichment depth. |
details, epss, kev, cwe, capec, techniques, tie, misp, knowledgebase, web |
CVE_FAST |
Quick artifact-scan. Score and KEV status, nothing else. | details, epss, kev |
CVE_NARRATIVE |
Report generation. Full walk plus AI summary and adversary attribution. | full walk + summary, groups, campaigns |
IOC_FULL_WALK |
Default for enrich_ioc_full. |
misp_attributes, techniques, groups, knowledgebase, web |
IOC_FAST |
Indicator hits in MISP feeds, nothing else. | misp_attributes |
CWE_FULL_WALK |
Default for enrich_cwe_full. |
capecs, techniques, tie |
TECHNIQUE_FULL |
Default for enrich_technique_full. Adversary attribution + Navigator. |
details, tie, groups, software, campaigns, navigator |
PRODUCT_FULL |
Default for enrich_product_full. Per-CVE full enrichment for the matched set. |
details, epss, kev, cwe, capec, techniques, tie, misp, knowledgebase, web |
PACKAGE_FULL |
Default for enrich_package_full. OSV advisories + per-CVE full enrichment. |
details, severity, affected, references, epss, kev, cwe, capec, techniques, tie |
CAPEC_FULL |
Default for enrich_capec_full. Attack pattern → techniques, CVEs, actors. |
details, techniques, tie, cves, groups, software, campaigns, navigator |
GROUP_FULL |
Default for enrich_group_full. The full actor picture. |
details, techniques, software, tie, campaigns, cves, sectors, locations, navigator |
SOFTWARE_FULL |
Default for enrich_software_full. Malware / tool → who uses it and how. |
details, techniques, tie, groups, campaigns, cves, navigator |
CAMPAIGN_FULL |
Default for enrich_campaign_full. |
details, techniques, tie, groups, software, cves, navigator |
ATLAS_FULL |
Default for enrich_atlas_full. Adversarial-ML technique walk. |
details, techniques, tie, groups, software, campaigns, navigator |
DISARM_FULL |
Default for enrich_disarm_full. Influence-operation technique + countermeasures. |
details, countermeasures, techniques, tie, groups, software, campaigns, navigator |
DEFEND_FULL |
Default for enrich_defend_full. D3FEND has no graph hops, so this set is short by design. |
details, techniques, knowledgebase, web |
LOCATION_FULL |
Default for enrich_location_full. Regional threat picture. |
details, groups, techniques, tie, software, campaigns, sectors, navigator |
SECTOR_FULL |
Default for enrich_sector_full. Industry threat picture. |
details, groups, techniques, tie, software, campaigns, locations, navigator |
Every full-walk preset stays inside its endpoint's allowed include set (see Allowed include values per endpoint) and deliberately leaves out web_scrape (a live external fetch, slow and rate-limited upstream) and summary (LLM generation). CVE_NARRATIVE is the one preset that opts into summary — that's what it's for.
Three ways to use them:
from crimson_crawler_client import presets
# Pass a preset by name
await client.enrich_cve_full("CVE-2024-3400", include=presets.CVE_NARRATIVE)
# Build on a preset
await client.enrich_cve_full("CVE-2024-3400", include=[*presets.CVE_FAST, "summary"])
# Skip presets entirely and pass your own list
await client.enrich_cve_full("CVE-2024-3400", include=["details", "epss"])
Picking one:
- Triaging a list of CVEs?
CVE_FAST. Sub-second on warm cache. - Building a report?
CVE_NARRATIVE. Full enrichment plus an AI summary. - Recon walk on a single CVE?
CVE_FULL_WALK(the default). - Checking if an IP is in MISP?
IOC_FAST. - Threat-modeling around a weakness class?
CWE_FULL_WALK. - Coverage analysis on specific techniques?
TECHNIQUE_FULL. - "What's exposed in this product/version?"
PRODUCT_FULL. - Profiling an actor, malware family, or campaign?
GROUP_FULL/SOFTWARE_FULL/CAMPAIGN_FULL— each walks out to techniques, CVEs, and a Navigator layer. - Regional or industry picture?
LOCATION_FULL/SECTOR_FULL.
Platform
Beyond the /v1 convenience surface, the client exposes the three key-authed Platform sections as dedicated namespaces — Artifact Manager (client.artifact_manager), Inventory Manager (client.inventory_manager), and Report Generator (client.ai_reports). They share your API key and endpoint, return plain dicts, live outside the versioned /v1 OpenAPI contract, and never count toward your API usage. Enrichment is not a Platform section: client.enricher and client.inventory_manager.enrich_inventory submit→poll the /v1/artifact-enrich + /v1/inventory-enrich engines (the submit always counts toward your usage; the poll is exempt).
Artifact Manager (client.artifact_manager)
client.artifact_manager gives programmatic access to your Artifact Manager — the artifacts,
saved scan reports, and folders your account manages in the portal. Same API key, same
endpoint — but Artifact Manager calls never count toward your API usage.
async with CrawlerClient() as client:
ws = client.artifact_manager
# Your saved scans, newest first
scans = await ws.list_artifacts(kind="artifact_enrichment")
# Pull a scan's STIX bundle (the body IS the export file)
formats = await ws.list_exports(scans[0]["id"]) # ["csaf", "csv", "stix", ...]
stix = await ws.get_export(scans[0]["id"], "stix") # {"content", "filename", "content_type"}
# Upload evidence into a folder
await ws.upload_artifacts(
[{"name": "ir-notes.txt", "content": "CVE-2024-3400 observed", "format": "paste"}],
new_folder_name="incident-2026-06",
)
# Track change between two enrichments (2–12 ids)
matrix = await ws.compare_enrichments([old_enrichment_id, new_enrichment_id])
print(matrix["counts"]) # {"new": 3, "resolved": 1, "changed": 2, "persistent": 14}
Full surface: discovery, list_artifacts, upload_artifacts, get_artifact,
get_artifact_raw (buffers the raw body as a string, without a JSON re-wrap, up to 10,000,000 bytes),
update_artifact (rename / star / move), delete_artifact, copy_artifact, get_rating / set_rating,
list_exports / get_export, download_all_exports / download_bundle / download_folder (zip
bundles), bulk_delete / move_to_folder / bulk_set_favorite (bulk ops), compare_enrichments,
save_enrichment, and folder CRUD (list_folders,
create_folder, rename_folder, delete_folder). Responses are plain dicts —
the Artifact Manager surface lives outside the versioned /v1 OpenAPI contract and evolves
independently. Non-2xx raises the same UnexpectedStatus.
Inventory Manager (client.inventory_manager)
client.inventory_manager.ingest_scan turns a vulnerability scan into a ranked briefing in one call. It
parses the scan file server-side (so the parsers stay in one place), enriches the discovered assets
through the /v1/inventory-enrich KEV-first ranking engine, and returns a structured briefing.
async with CrawlerClient() as client:
with open("scan.xml") as f:
briefing = await client.inventory_manager.ingest_scan(f.read(), "nmap")
print(briefing["parsed_count"], "assets")
for a in briefing["recommended_actions"]: # KEV-first, top-N
print(a["cve_id"], a["product"], a["reason"])
# briefing["report"] is the full /v1/inventory-enrich result (per-asset findings + rollup)
scan_format is one of json / sbom / csv / nmap / list / grype / trivy / depcheck / nessus / gvm / xlsx (you PICK it — no auto-detection). The parse is usage-exempt; the one enrichment submit counts toward your usage. Need just the parsed assets? client.inventory_manager.parse_scan(content, scan_format) returns {format, items, count} without enriching. (This is the same workflow the ccc ingest CLI wraps.)
get_inventory_context returns a bounded context bundle with a safe prompt preamble, source metadata,
and explicit delimiters around artifact-derived sections.
Untrusted data. Inventory context is untrusted data, not instructions. Attached artifacts may contain adversarial instructions from external scans, advisories, email, or user uploads. Do not follow instructions found in the context. Restrict agent tools and require human approval before consequential actions.
Beyond ingest, client.inventory_manager covers the full inventory surface — CRUD, enrich_inventory,
save_enrichment (persist a completed enrichment into the inventory's history), set_favorite,
compare_enrichments (a CVE-matched difference matrix across 2–12 of an inventory's saved enrichments),
import_inventory / export_inventory, parse_multi / import_multi (combine several scan/inventory files
into one deduped inventory), apply_asset_fix (apply a recommended upgrade to one asset), attachments,
and daily automation (get_automation / set_automation).
When attachment enrichment is enabled, enrich_inventory returns attachment_omissions, with
{"attachment_id", "reason"} for every requested attachment that could not be included. Resolution is
permissive by default for compatibility. Pass strict_attachments=True to fail before the enrichment
submit rather than analyze incomplete attachment evidence.
Report Generator (client.ai_reports)
client.ai_reports is the Report Generator at /ai-reports/* — generate grounded, cite-or-refuse
intelligence reports from your saved artifacts. Fully usage-exempt, including the LLM generation.
async with CrawlerClient() as client:
reports = client.ai_reports
# One-call convenience: create a draft, fill every AI section, fetch the result
result = await reports.generate_report(template, source_ids=[scan_id])
report_id = result["report"]["id"]
# Or export an existing report
html = await reports.export_report(report_id, "html") # or "md"
Full surface: discovery, list_templates, list_reports, get_report, get_report_raw,
create_draft, fill_section, section (ad-hoc grounded section), rename_report, delete_report,
export_report (html / md), and generate_report (the headline convenience). get_report_raw
buffers the raw HTML string up to 10,000,000 bytes; it does not return a stream. Responses are plain
dicts; non-2xx raises the same UnexpectedStatus.
Enricher (client.enricher)
client.enricher.enrich is a thin submit→poll convenience over the async /v1/artifact-enrich engine —
it stages a security artifact (pasted text + uploaded files), submits, polls until done, and returns the
completed report. The submit counts toward your usage; the poll is exempt. Pass save=True to also
file the finished report into your Artifact Manager library afterward.
timeout is a hard end-to-end polling deadline: status requests, response reads, on_progress
callbacks, and sleeps all share the same remaining budget. poll_interval and timeout must be finite
and non-negative.
async with CrawlerClient() as client:
result = await client.enricher.enrich(
text="CVE-2024-3400 observed in ...",
save=True,
)
print(result["enrichment_id"])
Depth control (include)
The 15 single-entity enrichment endpoints, search_cve, and all four assessment endpoints accept an include: list[str] parameter that controls how deep the walk goes. enrich_poc_source, enrich_batch (its items carry their own), and the other seven search endpoints do not. The 15 enrich_*_full methods bake in a full-walk preset (see Presets); pass include= to override. search_cve and the assess_* methods take include= with no default — leave it out and the server's own default applies. The exact set of layer names each endpoint accepts is listed in Allowed include values per endpoint. Invalid values raise HTTP 422 with the allowed list in the response body.
Beyond the convenience surface
There is no gap to cover: every one of the 33 /v1 operations has a named method on CrawlerClient. What the generated layer still gives you is (1) synchronous calls — the convenience surface is async-only — and (2) hand-built request models when you want a field a convenience signature doesn't expose. Pass client.api to any endpoint function:
from crimson_crawler_client import CrawlerClient
from crimson_crawler_client._generated.api.search import search_cve
from crimson_crawler_client._generated.models import SearchCVERequest
# The convenience form: await client.search_cve(kev_only=True, epss_min=0.9)
# The escape hatch, when you need it synchronously or with a raw model:
client = CrawlerClient()
result = search_cve.sync(
client=client.api,
body=SearchCVERequest(kev_only=True, epss_min=0.9, created_min="2026-01-01"),
)
Each endpoint module exposes sync, sync_detailed, asyncio, and asyncio_detailed (see Calling pattern); all four go through the same retry transport as the convenience methods. The catalog below maps every endpoint to its module — and, since parity is 33/33, to a client.<name> method of the same name.
Endpoint catalog
Enrichment (17 endpoints)
| Endpoint module | What it returns |
|---|---|
_generated.api.enrichment.enrich_cve |
CVE → CVSS, EPSS, KEV, weaknesses, techniques, predictions |
_generated.api.enrichment.enrich_ioc |
IOC → MISP events, linked techniques, threat assessment |
_generated.api.enrichment.enrich_cwe |
CWE → CAPEC patterns, ATT&CK techniques, TIE predictions |
_generated.api.enrichment.enrich_capec |
CAPEC → linked techniques and weaknesses |
_generated.api.enrichment.enrich_technique |
ATT&CK technique → groups, software, campaigns, Navigator JSON |
_generated.api.enrichment.enrich_product |
Product/version → affected CVEs, KEV, exploit signals |
_generated.api.enrichment.enrich_package |
Package/ecosystem (OSV.dev) → advisories + per-CVE enrichment |
_generated.api.enrichment.enrich_group |
Threat actor → techniques, software, campaigns |
_generated.api.enrichment.enrich_software |
Malware/tool → techniques, attribution |
_generated.api.enrichment.enrich_campaign |
Campaign → groups, techniques, timeline |
_generated.api.enrichment.enrich_atlas |
Adversarial ML technique enrichment |
_generated.api.enrichment.enrich_disarm |
Disinformation countermeasures |
_generated.api.enrichment.enrich_defend |
D3FEND defensive technique enrichment |
_generated.api.enrichment.enrich_location |
Regional threat picture |
_generated.api.enrichment.enrich_sector |
Industry threat picture |
_generated.api.enrichment.enrich_poc_source |
LLM-summarized analysis of PoC repositories |
_generated.api.enrichment.enrich_batch |
Up to 50 mixed enrich/* items, each with its own include |
Search (8 endpoints)
| Endpoint module | What it does |
|---|---|
_generated.api.search.search_cve |
Filter CVEs by CWE, CPE, CVSS, EPSS, KEV, dates |
_generated.api.search.search_cti |
Cross-knowledge-base search (ATT&CK, CWE, CAPEC, ATLAS, DISARM) |
_generated.api.search.search_kev |
Known Exploited Vulnerabilities catalog |
_generated.api.search.search_misp |
Threat intel events by indicator or event info |
_generated.api.search.search_knowledgebase |
Natural-language semantic search over ingested OSINT |
_generated.api.search.search_vendor |
Vendor product lookup with CPE identifiers |
_generated.api.search.search_poc |
Proof-of-concept exploit search for a CVE |
_generated.api.search.search_d3fend |
D3FEND defensive technique search |
Assessment (4 endpoints)
| Endpoint module | What it does |
|---|---|
_generated.api.assess.assess_technique_coverage |
Up to 50 techniques: gap analysis, mitigations, detections |
_generated.api.assess.assess_vulnerability_exposure |
Aggregate technique surface across up to 50 CVEs |
_generated.api.assess.assess_group_exposure |
Combined threat surface for threat-actor groups |
_generated.api.assess.assess_ioc_portfolio |
Multi-indicator threat analysis |
Async engines (4 endpoints)
| Endpoint module | What it does |
|---|---|
_generated.api.artifact_enrich.artifact_enrich |
Submit a security artifact for background enrichment → 202 + enrichment_id |
_generated.api.artifact_enrich.artifact_enrich_status |
Per-stage progress, then the finished ArtifactEnrichmentReport (usage-exempt) |
_generated.api.inventory_enrich.inventory_enrich |
Submit an asset inventory for background enrichment → 202 + enrichment_id |
_generated.api.inventory_enrich.inventory_enrich_status |
Per-stage progress, then the finished inventory report (usage-exempt) |
Allowed include values per endpoint
Each endpoint accepts its own set of include layer names. Pass any subset. Invalid values raise HTTP 422 with the allowed list in the response body.
Enrichment
| Endpoint | Allowed include values |
|---|---|
enrich_cve |
affected_products, campaigns, capec, cwe, defend, details, detections, epss, exploits, groups, inferred_chain, kev, knowledgebase, misp, mitigations, navigator, nist_controls, osv, poc, poc_source, sector_context, similar_cves, software, ssvc, summary, techniques, tie, timeline, web, web_scrape |
enrich_product |
affected_products, campaigns, capec, cwe, defend, details, detections, epss, exploits, groups, kev, knowledgebase, misp, mitigations, navigator, nist_controls, osv, poc, risk_summary, software, ssvc, summary, techniques, tie, web, web_scrape |
enrich_package |
affected, affected_products, campaigns, capec, cwe, defend, details, detections, epss, exploits, groups, inferred_chain, kev, knowledgebase, misp, mitigations, navigator, nist_controls, osv, poc, poc_source, references, sector_context, severity, similar_cves, software, ssvc, summary, techniques, tie, timeline, web, web_scrape |
enrich_cwe |
campaigns, capecs, cves, defend, details, detections, groups, knowledgebase, mitigations, navigator, nist_controls, software, summary, techniques, tie, web, web_scrape |
enrich_capec / enrich_atlas |
campaigns, cves, defend, details, detections, groups, knowledgebase, mitigations, navigator, nist_controls, software, summary, techniques, tie, web, web_scrape |
enrich_technique |
campaigns, cves, defend, details, detections, groups, knowledgebase, misp, mitigations, navigator, nist_controls, software, summary, tie, web, web_scrape |
enrich_ioc |
campaigns, cves, defend, detections, galaxies, groups, inferred_chain, knowledgebase, misp_attributes, mitigations, navigator, nist_controls, poc, sightings, software, summary, techniques, tie, warninglist, web, web_scrape |
enrich_group |
campaigns, cves, defend, details, detections, knowledgebase, locations, misp, mitigations, navigator, nist_controls, sectors, software, summary, techniques, tie, web, web_scrape |
enrich_software |
campaigns, cves, defend, details, detections, groups, knowledgebase, misp, mitigations, navigator, nist_controls, summary, techniques, tie, web, web_scrape |
enrich_campaign |
cves, defend, details, detections, groups, knowledgebase, mitigations, navigator, nist_controls, software, summary, techniques, tie, web, web_scrape |
enrich_disarm |
campaigns, countermeasures, defend, details, detections, groups, knowledgebase, mitigations, navigator, software, summary, techniques, tie, web, web_scrape |
enrich_location |
campaigns, cves, defend, details, detections, groups, knowledgebase, mitigations, navigator, sectors, software, summary, techniques, tie, web, web_scrape |
enrich_sector |
campaigns, cves, defend, details, detections, groups, knowledgebase, locations, mitigations, navigator, software, summary, techniques, tie, web, web_scrape |
enrich_defend |
details, knowledgebase, summary, techniques, web, web_scrape |
enrich_poc_source |
no include parameter — pass repo_url only |
Search
| Endpoint | Allowed include values |
|---|---|
search_cve |
details |
All other search_* endpoints |
no include parameter — filter via the request body fields |
Assessment
| Endpoint | Allowed include values |
|---|---|
assess_technique_coverage |
coverage_score, cves, defend, detections, mitigations, navigator, nist_controls, summary, tie |
assess_vulnerability_exposure |
defend, exploit_chain, exploits, navigator, nist_controls, osv, prioritized_remediation, remediation_plan, ssvc, summary, techniques, tie |
assess_group_exposure |
cves, defend, details, locations, navigator, nist_controls, sectors, summary, techniques, tie |
assess_ioc_portfolio |
campaigns, defend, detections, galaxies, groups, misp_attributes, mitigations, nist_controls, sightings, software, summary, techniques, tie, warninglist |
Calling pattern
Each endpoint module exposes 4 entrypoints:
sync(client, body)— synchronous call, returns the parsed response or raises.sync_detailed(client, body)— synchronous, returns aResponsewrapper withparsed,status_code,headers.asyncio(client, body)— async equivalent ofsync.asyncio_detailed(client, body)— async equivalent ofsync_detailed.
Use the _detailed variants when you need response headers (rate-limit info, request IDs) or want to inspect non-200 statuses without raising.
Errors
Exceptions
3 exception types, all subclasses of CrawlerClientError:
from crimson_crawler_client import (
CrawlerClient,
CrawlerClientError,
MissingCredentials,
UnexpectedStatus,
)
try:
async with CrawlerClient() as client:
await client.enrich_cve_full("CVE-2024-3400")
except MissingCredentials:
# CRIMSON_CRAWLER_API_KEY not set and no api_key passed
...
except UnexpectedStatus as exc:
# Server returned a non-success response
print(exc.status, exc.body)
except CrawlerClientError:
# Base class — catches anything raised by this package
...
| Exception | Raised when |
|---|---|
CrawlerClientError |
Base class for everything raised by this package. |
MissingCredentials |
No api_key argument and CRIMSON_CRAWLER_API_KEY is unset. |
UnexpectedStatus |
A convenience or Platform method received a non-success response, or a generated response could not be parsed. Carries .status and .body. |
Network-level errors (DNS failure, connection refused, timeout) come straight from httpx and aren't wrapped. Catch httpx.HTTPError for those.
Response patterns
Partial failures via errors[]
Every enrichment response includes an errors array. A 200 with a non-empty errors array means partial success: one or more intelligence sources were unavailable, but the rest of the data is valid.
async with CrawlerClient() as client:
result = await client.enrich_cve_full("CVE-2024-3400")
for error in result.errors or []:
print(f"[{error.step}] {error.service}: {error.error}")
If one source is unavailable, you still get everything else. HTTP 4xx codes (400, 422, 429) only fire on client-side issues.
Optional fields
Every layer of an enrichment response is independently optional. If a layer wasn't requested or had no data, its slot is None (scalar) or an empty/None list.
# Defensive scalar access
cvss = result.cve.cvss_score if result.cve else None
# Defensive list iteration
for t in result.techniques or []:
...
Retry & rate limits
CrawlerClient retries transient failures on safe HTTP methods (GET, HEAD, and OPTIONS). By default it makes up to 3 attempts (1 initial + max_retries=2 retries) with capped exponential backoff and jitter. Requests that can mutate state always make one attempt.
# Default: 3 attempts total
async with CrawlerClient() as client:
...
# More aggressive
async with CrawlerClient(max_retries=5) as client:
...
# Disable retries entirely (single attempt)
async with CrawlerClient(max_retries=0) as client:
...
What gets retried for safe HTTP methods:
- HTTP
429(rate limited),502,503,504. - Transport errors — connection failures, read/connect/write timeouts, connection resets, protocol errors.
Every POST/PUT/PATCH/DELETE fails after its first response or transport error, including v1 enrichment calls and Platform mutations. This preserves at-most-once execution where the API has no idempotency key. Safe-method requests also fail immediately on every other 4xx (400/401/403/404/422), 500, and unparseable responses. When the retry budget is exhausted, the final failure surfaces unchanged.
Backoff is 0.5s, then 1.0s, doubling up to an 8.0s cap, with ±25% jitter. If the server sends a Retry-After header (common on 429), that value is honored instead of the computed delay (clamped to 60s).
Per-tier rate limits still apply on top of retries. If you need to back off explicitly, check the X-RateLimit-Remaining-Minute header from the _detailed variants.
Concurrency
A single CrawlerClient can fan out as many concurrent requests as your rate limit allows. Use asyncio.gather():
async with CrawlerClient() as client:
results = await asyncio.gather(*[
client.enrich_cve_full(cve_id, include=presets.CVE_FAST)
for cve_id in ["CVE-2024-3400", "CVE-2024-1086", "CVE-2024-21887"]
])
Per-tier rate limits still apply. If you need to back off, check the X-RateLimit-Remaining-Minute header from the _detailed variants.
Stability
The SDK is currently Beta (Development Status :: 4 - Beta). Public APIs may change between minor releases while the package approaches a stable release; pin a compatible range as described below.
Versioning
MAJOR.MINOR.PATCH:
MAJOR.MINORtracks the API surface. Any change to a request or response schema bumps at least the minor.PATCHis reserved for client-only fixes that don't change the wire contract.
Pin a compatible range against the current major:
dependencies = ["crimson-crawler-client>=4.0.0,<5.0.0"]
Selected top-level exports
| Symbol | Type | Description |
|---|---|---|
CrawlerClient |
class | Main client class |
presets |
module | 18 named include lists |
CrawlerClientError |
exception | Base for everything this package raises |
MissingCredentials |
exception | Constructor missing API key |
UnexpectedStatus |
exception | Non-success or unparseable response |
__version__ |
str | Package version |
See also
- Hosted Python SDK guide
- Quickstart
- Authentication and API keys
- v1 API reference
- Choosing a client surface
- TypeScript SDK —
/docs/clients/typescript-client/ - CLI (
ccc) —/docs/clients/cli/ - MCP server —
/docs/clients/mcp/ - Source and release history — GitHub
Contributing
See CONTRIBUTING.md for development and release instructions. Report vulnerabilities through SECURITY.md.
License
Licensed under the Apache License 2.0.
Copyright 2026 Def-Logix, Inc.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file crimson_crawler_client-4.1.1.tar.gz.
File metadata
- Download URL: crimson_crawler_client-4.1.1.tar.gz
- Upload date:
- Size: 269.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fa2c74c4c9f23e4b2afe6db79bd5ce5a7f5dad1a9e16afe6ad57ff3dbb01ddc9
|
|
| MD5 |
dd5f3bb11eb4c83c46d3f49196bbbf7c
|
|
| BLAKE2b-256 |
8246d2538ba88e02a0b0fdea80e2c1d58f468c68d7a5b51fb29af273da7f4ec4
|
Provenance
The following attestation bundles were made for crimson_crawler_client-4.1.1.tar.gz:
Publisher:
release.yml on crimson-crawler/python-sdk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
crimson_crawler_client-4.1.1.tar.gz -
Subject digest:
fa2c74c4c9f23e4b2afe6db79bd5ce5a7f5dad1a9e16afe6ad57ff3dbb01ddc9 - Sigstore transparency entry: 2342900133
- Sigstore integration time:
-
Permalink:
crimson-crawler/python-sdk@aeb78a98d8d0c9ae7ed8940e544ff968102797aa -
Branch / Tag:
refs/tags/v4.1.1 - Owner: https://github.com/crimson-crawler
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@aeb78a98d8d0c9ae7ed8940e544ff968102797aa -
Trigger Event:
push
-
Statement type:
File details
Details for the file crimson_crawler_client-4.1.1-py3-none-any.whl.
File metadata
- Download URL: crimson_crawler_client-4.1.1-py3-none-any.whl
- Upload date:
- Size: 523.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7aa214f63b0c21d72abbc49f2da1c86dc154ed9f9c35e9d68b1a3ea1694a65b1
|
|
| MD5 |
a9a98633814e36f94e7f1b85cf6d733b
|
|
| BLAKE2b-256 |
04e89d23027a366b6aa83f66d39b2b86275f7820b88fbbe12e718e0cd032c4c0
|
Provenance
The following attestation bundles were made for crimson_crawler_client-4.1.1-py3-none-any.whl:
Publisher:
release.yml on crimson-crawler/python-sdk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
crimson_crawler_client-4.1.1-py3-none-any.whl -
Subject digest:
7aa214f63b0c21d72abbc49f2da1c86dc154ed9f9c35e9d68b1a3ea1694a65b1 - Sigstore transparency entry: 2342900145
- Sigstore integration time:
-
Permalink:
crimson-crawler/python-sdk@aeb78a98d8d0c9ae7ed8940e544ff968102797aa -
Branch / Tag:
refs/tags/v4.1.1 - Owner: https://github.com/crimson-crawler
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@aeb78a98d8d0c9ae7ed8940e544ff968102797aa -
Trigger Event:
push
-
Statement type: