Write Evidence Records
This guide walks you through posting a release-evidence record back into CertHub from your CI pipeline or release process. This is the main pattern for connecting test or build results back to CertHub without copying the entire artifact tree into it.
When to use this
Use this when your engineering process produces artifacts outside CertHub and you want CertHub to keep a short controlled record that answers:
- what version was built
- which evidence belongs to it
- where the full evidence lives
Prerequisites
- A CertHub API key (Authentication)
- The IDs you will need (IDs and relationships):
- KT revision ID of your Release Evidence knowledge topic
- KU revision ID resolved from the Tech Doc API
- Product history ID from the dashboard URL
- A multi-record knowledge topic in CertHub for the evidence you want to track
Step 1: Create a Release Evidence KT in CertHub
Before you can write records via the API, create a knowledge topic in CertHub with the fields you actually want to review.
| Field (UI label) | certhub-key | Purpose |
|---|---|---|
| Release number | release-number | Version identifier |
| Release ID | release-id | Git commit SHA or build identifier |
| Generated at | generated-at | UTC ISO timestamp |
| Evidence URL | evidence-url | Link to the full evidence |
| Notes (or Details) | details | Short plain-text summary |
The UI label can be Notes; the stable certhub-key must be details. Set certhub-key on each field so your integration can resolve form keys from the schema instead of hardcoding opaque widget IDs.
For more detail on that mapping, see Form field keys.
Step 2: Resolve the form keys from the schema
# pip install requests
import requests
API_KEY = "YOUR_API_KEY"
TECHDOC_BASE = "https://techdoc.prod.certhub-containers.containers.certhub.tech"
RECORDS_BASE = "https://records.prod.certhub-containers.containers.certhub.tech"
HEADERS = {"X-API-Key": API_KEY}
KT_REVISION_ID = "YOUR_RELEASE_EVIDENCE_KT_REVISION_ID"
resp = requests.get(f"{TECHDOC_BASE}/kt/{KT_REVISION_ID}", headers=HEADERS)
resp.raise_for_status()
kt = resp.json()
schema = kt.get("knowledge_topic_schema", {})
components = schema.get("components", [])
certhub_key_to_form_key = {}
for component in components:
form_key = component.get("key")
if not form_key:
continue
props = component.get("properties", {})
ck = props.get("certhub-key")
if ck:
certhub_key_to_form_key[ck] = form_key
Step 3: Get the KU revision ID
KU_HISTORY_ID = "YOUR_KU_HISTORY_ID"
resp = requests.get(f"{TECHDOC_BASE}/ku/{KU_HISTORY_ID}", headers=HEADERS)
resp.raise_for_status()
ku = resp.json()
ku_revision_id = ku["id"]
Step 4: Build and POST the record
PRODUCT_HISTORY_ID = "YOUR_PRODUCT_HISTORY_ID"
def form_key(certhub_key):
fk = certhub_key_to_form_key.get(certhub_key)
if not fk:
raise KeyError(f"No form field with certhub-key '{certhub_key}' in schema")
return fk
payload = {
"name": "Release Evidence 1.0.0",
"form": {},
"data": {
form_key("release-number"): "1.0.0",
form_key("release-id"): "abc123def456",
form_key("generated-at"): "2026-08-19T09:00:00Z",
form_key("evidence-url"): "https://github.com/your-org/your-repo/actions/runs/123456789",
form_key("details"): "VERIFIED\nSystem Requirements: 4\nVerified: 4/4\nPassed: 4 Failed: 0",
},
"context": {
"knowledge_unit_topic_id": KT_REVISION_ID,
"knowledge_unit_id": ku_revision_id,
"linked_product": PRODUCT_HISTORY_ID,
},
"read_only": True,
}
response = requests.post(f"{RECORDS_BASE}/records/", json=payload, headers=HEADERS)
response.raise_for_status()
created = response.json()
print(f"Created record: {created['_id']}")
Setting read_only: true prevents accidental edits in the UI. The record becomes a controlled evidence entry.
When to write and when not to
A sensible default:
- write to CertHub for full releases
- build evidence for release candidates, but do not post it yet
- keep pull request and branch evidence in CI only
Cadence (CertHub’s public engineering example) follows this release-only pattern. See Working example.