Our engineers set up and run your first chatbot / LLM security scan. Get in touch

AI Bill of Materials API

← All docs

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.

RouteScope
GET /api/v2/assetsassets:read
GET /api/v2/ai-bomassets: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_classSource kind values
modelmodel_provider, self_hosted_model, fine_tuned_model, embedding_model
datasetdata_source
vector_storevector_database
retrieval_pipelinerag_system
prompt_assetprompt_gateway
endpointLLM endpoints registered for scanning
toolMCP servers and agents registered for scanning
otherA 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.

FieldMeaning
discovery_sourceauto_probe, cloud_posture, agent_enum, customer_declared, api_pull. For endpoints, how the endpoint was registered.
verification_statusverified when a person has confirmed the entry, otherwise unverified. Null for endpoints, which carry no verification workflow.
last_verified_atWhen that confirmation was recorded.
last_seen_atWhen the component was last observed by a probe or agent. Null for entries nothing has looked at since declaration.
first_recorded_atWhen the row entered the inventory.
last_modified_atLast 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

StatuserrorCause
400invalid_formatformat is not json or csv
400invalid_kindkind is not a plain lower-case identifier
403scope_missingToken lacks assets:read
429rate_limitedPer-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] .