SBOM ingestion
POST /v1/sbom accepts a CycloneDX JSON document as the request body. Each flattened component becomes one result row. Mapped rows call the same check path as GET /v1/check. Unmapped rows still return and still bill.
Container and OS SBOMs
A Trivy or Syft image BOM is mostly pkg:deb, pkg:apk, and pkg:oci. Every row is billed. Almost none are mapped. Use lockfile CycloneDX (npm or PyPI) or the GitHub Action lockfile scan. A golang-only BOM is a valid coverage probe and returns 200 with mapped_count 0.
CycloneDX JSON 1.3 through 1.6
Send the BOM object itself (bomFormat at the top level). No wrapper. JSON only. XML returns 400. SPDX is out of v1. GitHub Dependency Graph / repository SBOMs are SPDX. Convert to CycloneDX or use lockfile mode in the GitHub Action.
- Cap: 2,000 flattened components (includes
metadata.componentand nestedcomponents). Over cap is 400, not billed. - Body cap: 4 MiB. Over size is 413, not billed.
- Content-Type may be
application/jsonorapplication/vnd.cyclonedx+json. Missing or charset suffixes are fine. scope: excludedandoptionalrows are returned. BOMvulnerabilities[]is ignored.- Nesting deeper than 32 levels is 400, not billed. Nodes are not dropped silently.
- No query parameters.
include=cvesis not available on this route.
A large unique scan can take tens of seconds. Cloudflare proxies api.attestd.io with a roughly 100s timeout.
What maps onto a check
| purl type | v1 behavior |
|---|---|
pkg:npm | Mapped. Supply-chain check with ecosystem=npm. Unwatched names return supported: false. |
pkg:pypi | Mapped. Supply-chain check with ecosystem=pypi. |
pkg:generic | Mapped only when the name is an Attestd CVE product slug. Aliases such as postgres work. OS names (linux, windows, ubuntu, debian, macos, kernel) are never mapped. |
golang, maven, deb, apk, oci, cargo, nuget, others | outside_coverage, still billed. Telemetry records the type. |
Placeholder versions (unspecified, unknown, *, latest) and ranges are invalid_identity. A component with no purl is missing_purl. We do not guess ecosystem from name alone.
One row, one call
Every returned row bills one call, including outside_coverage. Internal per-item errors are not billed. Quota is checked before any check work. If the BOM needs more calls than you have left, the response is 429 and nothing is billed.
Free tier is 1,000 calls per month and 60/min. A 2,000-component BOM does not fit Free. Solo includes 10,000 calls, enough for five max-size uploads. Duplicate identities in one BOM are two rows and two bills. The check itself runs once for that identity in the request.
Upload a BOM
Keys scoped to v1.check may call this route. Scope id is v1.sbom.
curl -X POST "https://api.attestd.io/v1/sbom" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/vnd.cyclonedx+json" \
--data @bom.jsonMinimal bom.json:
{
"bomFormat": "CycloneDX",
"specVersion": "1.5",
"metadata": {
"component": {
"type": "application",
"name": "example-app",
"version": "1.0.0"
}
},
"components": [
{
"type": "library",
"name": "left-pad",
"version": "1.3.0",
"purl": "pkg:npm/[email protected]"
},
{
"type": "library",
"name": "requests",
"version": "2.32.3",
"purl": "pkg:pypi/[email protected]"
}
]
}Response leads with summary, then results in document order. Truncated:
{
"summary": {
"component_count": 3,
"mapped_count": 2,
"outside_coverage_count": 1,
"risk_state_counts": {
"critical": 0,
"high": 0,
"elevated": 0,
"low": 0,
"none": 2
},
"compromised_count": 0,
"billed_calls": 3,
"spec_version": "1.5"
},
"results": [
{
"bom_ref": null,
"purl": null,
"name": "example-app",
"version": "1.0.0",
"ecosystem": null,
"outside_coverage": true,
"outside_coverage_reason": "missing_purl",
"result": null,
"error": null
}
]
}| HTTP | When |
|---|---|
| 413 | Body larger than 4 MiB. Not billed. |
| 400 | Not JSON, XML, bad bomFormat, specVersion outside 1.3 to 1.6, zero components, more than 2,000 components, or nesting deeper than 32 levels. Not billed. |
| 401 / 403 | Auth or route scope. |
| 429 | Monthly quota, key cap, or Free 60/min, before checks run. Not billed. |
SPDX, CycloneDX XML, async job ids, SDK methods, MCP tools, GitHub Action SBOM input, and include=cves on this route.