An AI Bill of Materials is a structured inventory of everything a production AI system is built from: the models it calls, the corpora it retrieves over, the vector stores that hold them, the tools and MCP servers it can reach, and the endpoints it exposes. Auditors working through EU AI Act Annex IV technical documentation or ISO/IEC 42001 A.6.2 ask for exactly this.
Two routes cover it. /api/v2/assets returns the raw inventory rows as stored. /api/v2/ai-bom returns the assembled document: the same inventory plus the endpoints and agents under test, normalised onto one component shape and stamped with provenance.
| Route | Scope |
|---|---|
GET /api/v2/assets | assets:read |
GET /api/v2/ai-bom | assets:read |
Both are read-only. The document is derived from declared inventory, so there is nothing to write through it; assets are created in the console, where the per-kind form validates the attributes each kind expects.
Retrieve the AI-BOM
curl -sS "https://penaxtra.com/api/v2/ai-bom" \
-H "Authorization: Bearer $TOKEN"
{
"schema": "penaxtra.ai-bom",
"schema_version": "1.0",
"generated_at": "2026-08-04T18:42:11+00:00",
"tenant": { "id": "a31a99d7-...", "name": "Acme Bank" },
"summary": {
"component_count": 14,
"by_class": {
"dataset": 2, "model": 7, "prompt_asset": 1,
"retrieval_pipeline": 2, "vector_store": 2
},
"verified_count": 11
},
"components": [
{
"id": "6f2c1a90-...",
"component_class": "retrieval_pipeline",
"kind": "rag_system",
"name": "customer-support-rag",
"vendor": "langchain",
"region": "eu-central-1",
"endpoint_url": "https://rag.internal.acme/search",
"application": "Customer Support Assistant",
"data_classes": ["pii", "support"],
"tags": ["prod", "customer-facing"],
"attributes": { "top_k": 5, "chunk_size": 512 },
"risk_score": 34,
"provenance": {
"discovery_source": "agent_enum",
"verification_status": "verified",
"last_verified_at": "2026-07-30T09:14:00+00:00",
"last_seen_at": "2026-08-04T06:00:00+00:00",
"first_recorded_at": "2026-06-11T13:22:41+00:00",
"last_modified_at": "2026-07-30T09:14:00+00:00"
}
}
]
}
Schema and versioning
schema and schema_version let a consumer pin the shape it parses. Adding a field does not change the version. Removing a field, or changing its type, is a breaking change and does bump it. Read schema_version rather than inferring the shape from the fields you happen to see.
Component classes
component_class is the stable bucket to reason in. kind keeps the precise inventory term underneath it, so nothing is lost in the normalisation.
component_class | Source kind values |
|---|---|
model | model_provider, self_hosted_model, fine_tuned_model, embedding_model |
dataset | data_source |
vector_store | vector_database |
retrieval_pipeline | rag_system |
prompt_asset | prompt_gateway |
endpoint | LLM endpoints registered for scanning |
tool | MCP servers and agents registered for scanning |
other | A kind with no mapping yet |
A kind with no mapping is emitted as other rather than dropped. A document that silently omits a component is worse than one that admits it has no bucket for it, and an auditor comparing counts against the console would find the discrepancy either way.
Provenance
Every component carries a provenance block. This is what separates a bill of materials from a list: it records how each entry got there and whether anyone has stood behind it.
| Field | Meaning |
|---|---|
discovery_source | auto_probe, cloud_posture, agent_enum, customer_declared, api_pull. For endpoints, how the endpoint was registered. |
verification_status | verified when a person has confirmed the entry, otherwise unverified. Null for endpoints, which carry no verification workflow. |
last_verified_at | When that confirmation was recorded. |
last_seen_at | When the component was last observed by a probe or agent. Null for entries nothing has looked at since declaration. |
first_recorded_at | When the row entered the inventory. |
last_modified_at | Last edit to the row. |
summary.verified_count is the count of components whose verification_status is verified. Treat the gap between that and component_count as the review backlog.
Credentials
The document carries auth_method (the mechanism) and never the credential. Endpoint credentials are held in a sealed box and are not read on this path. Nothing in the AI-BOM is a secret, which is what makes it safe to hand to an auditor as-is.
CSV
curl -sS "https://penaxtra.com/api/v2/ai-bom?format=csv" \
-H "Authorization: Bearer $TOKEN" -o ai-bom.csv
The same components, one row each, in a fixed column order:
component_class,kind,name,vendor,region,endpoint_url,data_classes,tags,
application,discovery_source,verification_status,last_verified_at,
last_seen_at,first_recorded_at,risk_score,id
data_classes and tags are space-separated inside their cell. The attributes object has no fixed shape across kinds and is therefore omitted from CSV; take the JSON document when you need it.
The column order is stable across releases. New columns are appended, never inserted, so a consumer indexing by position keeps working.
List the raw inventory
curl -sS "https://penaxtra.com/api/v2/assets?kind=vector_database" \
-H "Authorization: Bearer $TOKEN"
Returns up to 500 rows as {"data": [...]}, ordered by kind then name. The fields are the stored columns rather than the normalised component shape: asset_kind, name, vendor, region, endpoint_url, data_classes, tags, attributes, risk_score, discovery_source, verification_status, last_verified_at, last_seen_at, created_at, updated_at.
kind is optional and restricts the result to one asset kind. An unknown kind is rejected rather than ignored, because silently returning the whole inventory to a caller who asked for one slice of it is the wrong default.
Console
The same two documents are downloadable from AI Asset Inventory at /app/assets, as ai-bom.json and ai-bom.csv. Console downloads and API reads both record an ai_bom.downloaded audit event carrying the format and component count. An AI-BOM is a complete map of a customer's AI estate, so copies of it are tracked the same way report downloads are.
Errors
| Status | error | Cause |
|---|---|---|
| 400 | invalid_format | format is not json or csv |
| 400 | invalid_kind | kind is not a plain lower-case identifier |
| 403 | scope_missing | Token lacks assets:read |
| 429 | rate_limited | Per-token bucket exhausted; see API rate limits |
Worked example
Fail a release when an unverified component reaches production:
set -euo pipefail
curl -sS "https://penaxtra.com/api/v2/ai-bom" \
-H "Authorization: Bearer $TOKEN" > ai-bom.json
UNVERIFIED=$(jq '[.components[]
| select(.provenance.verification_status != "verified")
| select(.tags | index("prod"))] | length' ai-bom.json)
if [ "$UNVERIFIED" -gt 0 ]; then
echo "$UNVERIFIED production components have not been verified" >&2
jq -r '.components[]
| select(.provenance.verification_status != "verified")
| select(.tags | index("prod"))
| " \(.component_class)\t\(.name)"' ai-bom.json >&2
exit 1
fi
Endpoints carry a null verification_status, so widen the filter to != "verified" only if you intend to include them; otherwise select on component_class first.
Related
Last reviewed: 2026-08-04. Reviewed by: Engineering. Content type: Developer documentation. Reach the maintainers: [email protected] .