OSPulse

CRA compliance workflow

The EU Cyber Resilience Act turns "we patch things eventually" into a set of dated, evidenced obligations. This guide walks the full loop in the OSPulse API: declare a product and its regime, attach release SBOMs, let the watcher classify findings, run the statutory reporting clocks, and export an evidence pack an auditor can read.

This is not legal advice

OSPulse is a tooling vendor, not your counsel. Everything below describes what the platform records, computes, and exports. Whether a given product falls in scope, which conformity route applies, and what you file with which authority are legal determinations for you and your advisers. Every duty, clock, and report pack OSPulse generates carries a paraphrase and a link to the primary source so you can check it against the regulation itself.

The dates that drive the work

MilestoneDateWhat changes
Entry into forceDecember 2024The Regulation is law; the transition period begins
Reporting obligations apply11 September 2026Actively exploited vulnerabilities and severe incidents must be reported
Main obligations apply11 December 2027The full essential-requirements, conformity, and support-period regime applies

The September 2026 date is the one that bites first, and it is the one with the tightest mechanics: awareness starts a clock measured in hours, not quarters. Everything else in this guide exists to make that clock survivable.

For the business-level overview — scope, who counts as a manufacturer, how OSPulse maps to the obligation set — see the CRA overview.

What OSPulse does and does not do

Automated by the platform. Component inventory per release, SBOM generation and freshness tracking, correlation of your components against vulnerability and active-exploitation intelligence, deterministic classification of findings, clock arithmetic from the awareness instant, assembly of stage report packs against the current field set, and a sealed evidence pack with a manifest.

Still yours. Declaring whether a product is placed on the EU market, declaring your regulatory regime, deciding what is genuinely out of scope and justifying it, signing off each report pack, actually submitting to the relevant authority, and every organisational duty the Regulation places on you as a manufacturer — secure-by-design process, support period commitments, coordinated disclosure policy.

Classification is deterministic, never AI

A finding's CRA classification is derived from exploitation evidence and confirmed component presence, using rules you can reproduce. No language model assigns it. That is deliberate: an obligation that starts a legal clock cannot rest on a probabilistic output.

End-to-end workflow

Register the product

A CRA product is the thing you place on the market — not a repository. Its EU market status is customer-declared and never inferred.

bash
curl -X POST https://api.ospulse.app/api/v1/cra/products \
  -H "Authorization: Bearer $OSPULSE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Atlas Gateway",
    "description": "Edge appliance firmware",
    "euMarketStatus": "PlacedOnEuMarket",
    "supportPeriodStart": "2026-01-15",
    "supportPeriodEnd": "2031-01-15"
  }'
json
{
  "id": "3e91b8a2-47cd-4f60-8b13-c5a29e7d0146",
  "name": "Atlas Gateway",
  "description": "Edge appliance firmware",
  "euMarketStatus": "PlacedOnEuMarket",
  "supportPeriodStart": "2026-01-15",
  "supportPeriodEnd": "2031-01-15",
  "releases": []
}

Declare the regulatory regime

The duty set you are held to differs by role. Manufacturer is the default and carries the full obligation set; OpenSourceSteward carries the lighter subset the Regulation defines for stewards. This is self-declared — OSPulse will not guess it for you.

bash
curl -X PATCH https://api.ospulse.app/api/v1/cra/products/$PRODUCT_ID/regime \
  -H "Authorization: Bearer $OSPULSE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "regime": "Manufacturer", "effectiveFromUtc": "2026-01-15T00:00:00Z" }'

The response echoes the regime plus a disclaimer string. That string is stored with the declaration and reproduced in the evidence pack, so the record shows what you were told when you made the call.

Create a release and attach its SBOM

Findings attach to releases, so nothing happens until a release exists. Pin it to the scan you shipped from and generate the SBOM:

bash
curl -X POST https://api.ospulse.app/api/v1/cra/products/$PRODUCT_ID/releases \
  -H "Authorization: Bearer $OSPULSE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "version": "4.2.0", "sourceScanId": "'"$SCAN_ID"'", "manualUpload": false }'
 
curl -X POST https://api.ospulse.app/api/v1/cra/releases/$RELEASE_ID/sbom \
  -H "Authorization: Bearer $OSPULSE_TOKEN"

Building outside OSPulse? Create the release with "manualUpload": true and upload the document to POST /api/v1/cra/releases/{releaseId}/sbom/upload?format=…. See Generate an SBOM for both paths in full.

Run the watcher

The watcher re-evaluates every release against current vulnerability and exploitation intelligence, creating and reclassifying findings. It runs on a schedule; trigger it on demand after a release or when new intelligence lands.

bash
curl -X POST "https://api.ospulse.app/api/v1/cra/watch/run?productId=$PRODUCT_ID" \
  -H "Authorization: Bearer $OSPULSE_TOKEN"
json
{
  "tenantId": "0c8b41d7-9e52-4a03-b6f8-2d1c74a9e058",
  "releasesEvaluated": 3,
  "findingsCreated": 7,
  "findingsReclassified": 2,
  "reportableCount": 1,
  "watchCount": 3,
  "trackCount": 11,
  "infoCount": 4,
  "runAtUtc": "2026-07-28T06:00:11Z"
}

Triage the findings

Findings carry a classification and a workflow state, and you filter on both.

classMeaning
ReportableActive-exploitation evidence and confirmed presence in your product — starts the legal clock
WatchExploitation evidence, but presence or affected-version range unconfirmed — triage urgently
TrackA known vulnerability with no exploitation evidence
InfoComponent intelligence only, such as abandonment or a maintainer change

Workflow states are Open, Acknowledged, NotInScope, and Closed.

bash
curl "https://api.ospulse.app/api/v1/cra/findings?class=Reportable&state=Open&productId=$PRODUCT_ID" \
  -H "Authorization: Bearer $OSPULSE_TOKEN"
json
[
  {
    "id": "d72f0a51-8c34-4b96-a0e7-5f138b2cd904",
    "productId": "3e91b8a2-47cd-4f60-8b13-c5a29e7d0146",
    "productReleaseId": "1f0a8c73-6b2d-4e19-a7c5-93b0d4f2e618",
    "componentPurl": "pkg:npm/example-parser@2.4.1",
    "affectedVersion": "2.4.1",
    "classification": "Reportable",
    "workflowState": "Open",
    "firstDetectedAt": "2026-07-27T22:41:09Z"
  }
]

GET /api/v1/cra/findings/{findingId} adds the exploitation signals with their provenance, the awareness event id, the reporting clock id, and the full audit timeline.

Acknowledge a finding with POST /api/v1/cra/findings/{findingId}/acknowledge. To rule one out, POST /api/v1/cra/findings/{findingId}/not-in-scope requires a mandatory justification and an expiresAt — suppression is time-boxed by design, so a decision made today does not silently persist for three years.

Log awareness and read the clock

Awareness is recorded automatically when a finding is classified Reportable. If you became aware earlier — a researcher emailed you before the feed caught up — log it explicitly. The earliest instant wins, and the value is write-once.

bash
curl -X POST https://api.ospulse.app/api/v1/cra/findings/$FINDING_ID/awareness \
  -H "Authorization: Bearer $OSPULSE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "awareAt": "2026-07-27T18:05:00Z" }'

Then read the derived deadlines:

bash
curl https://api.ospulse.app/api/v1/cra/findings/$FINDING_ID/clock \
  -H "Authorization: Bearer $OSPULSE_TOKEN"
json
{
  "id": "5b0c9e14-7a26-4f83-bd51-08c37e2a9f60",
  "kind": "Vulnerability",
  "stage": "EarlyWarningDue",
  "awarenessInstant": "2026-07-27T18:05:00Z",
  "earlyWarningDueAt": "2026-07-28T18:05:00Z",
  "notificationDueAt": "2026-07-30T18:05:00Z",
  "finalReportDueAt": "2026-08-10T18:05:00Z",
  "escalationHistory": [],
  "regulatoryReferenceVersion": "2026.06"
}

A vulnerability clock sets early warning at +24 hours and notification at +72 hours from awareness; the final-report deadline follows the availability of a corrective measure. Call POST /api/v1/cra/findings/{findingId}/clock/fix-available when a fix ships — that is what moves the clock out of FixPending and fixes the final deadline.

To see everything approaching across the tenant, poll GET /api/v1/cra/clocks/due?withinHours=48. Each row carries the due instant in UTC and in CET, because the deadline you are judged against is a Brussels one.

Generate a stage report pack

A report pack assembles the fields expected at a given submission stage, stamped with the field-set and regulatory-reference versions in force when it was generated. Stages are early-warning, notification, and final.

bash
curl -X POST https://api.ospulse.app/api/v1/cra/findings/$FINDING_ID/reports/early-warning \
  -H "Authorization: Bearer $OSPULSE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "signOffStatement": "Reviewed by security engineering; content verified against the advisory." }'
json
{
  "id": "9c4d1e77-0b58-42a3-8e6f-71a0c9d3b425",
  "craFindingId": "d72f0a51-8c34-4b96-a0e7-5f138b2cd904",
  "incidentDeclarationId": null,
  "stage": "EarlyWarning",
  "srpFieldSetVersion": "1.2",
  "regulatoryReferenceVersion": "2026.06",
  "signOffActorUserId": "62b0f8a4-1d39-4c07-95e2-8a4b1f602d3c",
  "signOffStatement": "Reviewed by security engineering; content verified against the advisory.",
  "signedOffAt": "2026-07-28T08:12:44Z",
  "generatedAt": "2026-07-28T08:12:44Z",
  "disclaimer": "…"
}

signOffStatement is mandatory and a service-account token is rejected with 403 — a regulatory submission is signed by a person. Download the pack as PDF with GET /api/v1/cra/reports/{packId}, or add ?format=json for the machine-readable companion. A finding with no started clock returns 404: generate the pack after awareness, not before.

Declare a severe incident

An incident is not a vulnerability finding — it gets its own declaration and its own clock kind, with a one-month final-report horizon.

bash
curl -X POST https://api.ospulse.app/api/v1/cra/incidents \
  -H "Authorization: Bearer $OSPULSE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Unauthorised access to update signing pipeline",
    "description": "Anomalous signing requests observed from a decommissioned runner.",
    "productId": "3e91b8a2-47cd-4f60-8b13-c5a29e7d0146"
  }'

The response carries a reportingClockId; from there the report-pack flow is identical.

Track duties and Annex I

Findings are only half the obligation surface. The duty checklist and the Annex I essential requirements are the other half, and they are the part a human maintains.

bash
curl "https://api.ospulse.app/api/v1/cra/duties?productId=$PRODUCT_ID" \
  -H "Authorization: Bearer $OSPULSE_TOKEN"
 
curl "https://api.ospulse.app/api/v1/cra/annex-i?productId=$PRODUCT_ID" \
  -H "Authorization: Bearer $OSPULSE_TOKEN"

Each item returns a referenceKey, a plain-language paraphrasedText, a primarySourceUrl pointing at the regulation, an applicableDate, a status, and the catalogVersion the paraphrase came from. Mark progress and link your evidence:

bash
curl -X PATCH https://api.ospulse.app/api/v1/cra/annex-i/$ITEM_ID \
  -H "Authorization: Bearer $OSPULSE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "Met",
    "linkedEvidenceArtifactIds": ["c31a7e60-5b44-4a1f-9d02-6e8f0b2c9a11"]
  }'

status is Met, Gap, or NotApplicable.

Export the evidence pack

The evidence pack is the artefact you hand to an auditor: a sealed archive with a manifest of everything it contains.

bash
curl -X POST https://api.ospulse.app/api/v1/cra/products/$PRODUCT_ID/evidence-pack \
  -H "Authorization: Bearer $OSPULSE_TOKEN"
json
{
  "id": "a08f62d1-4b7e-4390-91c5-fd23e086a7b4",
  "productId": "3e91b8a2-47cd-4f60-8b13-c5a29e7d0146",
  "generatedAt": "2026-07-28T08:30:02Z",
  "contentsManifest": [
    "products/atlas-gateway.json",
    "releases/4.2.0/sbom.cdx.json",
    "findings/reportable.json",
    "clocks/timeline.json",
    "duties/checklist.json",
    "annex-i/status.json"
  ]
}

Download the archive from GET /api/v1/cra/evidence-packs/{evidencePackId} — it streams as application/zip.

AI Act model dependencies

If a release pulls in machine-learning model artefacts, those carry their own transparency obligations. POST /api/v1/cra/products/{productId}/model-dependencies:detect scans the release for them, GET /api/v1/cra/products/{productId}/model-dependencies lists what was found with its capability class, and GET /api/v1/cra/products/{productId}/ai-act-duties returns the resulting duty items — patched the same way as Annex I items, via PATCH /api/v1/cra/ai-act-duties/{itemId}. An uncatalogued artefact is classed Unknown and produces a single generic review duty rather than a fabricated specific one.

Getting your data out

GET /api/v1/cra/export returns your entire CRA record — products, releases, SBOM documents, findings, exploitation signals, awareness events, reporting clocks, report packs, incident declarations, policy exceptions, duty items, Annex I items, and evidence packs — as plain JSON with no proprietary envelope. Legally required evidence should never be locked inside a vendor. Schedule it if you want a periodic off-platform copy.

For scheduled analytical extracts of the same finding data into a warehouse, see Export data to a warehouse.

Next steps