Skip to main content

API Authentication

CVD Portal provides two authentication methods for programmatic access: an OAuth 2.1 authorization server for autonomous agents and external applications, and static bearer API keys for direct scripts and CI/CD pipelines. Both methods enforce tenant isolation and role-based access control.

OAuth 2.1 Authorization Server​

The platform operates a standards-compliant OAuth 2.1 authorization server. Agents obtain short-lived, scope-limited access tokens bound to an authenticated user and company.

Protocol characteristics​

  • Grant types: authorization_code with PKCE, and refresh_token.
  • Mandatory PKCE: Proof Key for Code Exchange (RFC 7636) is mandatory for all clients. The code_challenge_method must be S256. The plain method is rejected.
  • Dynamic Client Registration: Clients can self-register using RFC 7591 at /api/oauth/register.
  • Single-use codes: An authorization code gives tokens one time only. A second exchange of the same code fails with invalid_grant, also when the two requests arrive at the same time.
  • Token rotation: Refresh tokens rotate on every use. Exchanging a refresh token issues a new access token and a new refresh token while revoking the previous one.
  • Refresh token reuse detection: Use each refresh token one time only. When a client presents a refresh token that a rotation replaced, the platform revokes all live access and refresh tokens that came from the same authorization, and answers invalid_grant (RFC 9700 section 4.14.2). The platform cannot tell if the client or an attacker sent the old token, so it ends the authorization. The app must ask for consent again, and the event is in the audit log as OAUTH_TOKEN_REVOKED. A refresh token that expired, or that was revoked for a different reason, gets invalid_grant and ends nothing. Two requests that arrive at the same time with the same refresh token give tokens to one request only. The other request gets invalid_grant, and the authorization stays live. A client that loses the response of a refresh must not send the old refresh token again. It must ask for consent again.
  • Token storage: All tokens and authorization codes are stored hashed (SHA-256) at rest. Plaintext credentials are never saved in the database.

Endpoints​

EndpointPathProtocolPurpose
Authorization/api/oauth/authorizeRFC 6749 / OAuth 2.1Authorization code request with PKCE S256
Token/api/oauth/tokenRFC 6749 / OAuth 2.1Code exchange and refresh token rotation
Dynamic registration/api/oauth/registerRFC 7591Client registration
Introspection/api/oauth/introspectRFC 7662Query token active status and metadata
Revocation/api/oauth/revokeRFC 7009Invalidate access and refresh tokens

Connected apps: see and revoke authorizations​

Go to Settings > Connected Apps to see each OAuth client that has access to your organisation. The page shows one row for each app and user. A row shows:

  • The app name and the hosts that get its authorizations, for example "Agent A, sends to agent.example.com".
  • The user who authorized the app.
  • The scopes the app holds.
  • The date of authorization.
  • The date the last token expires.

The app chooses its own name, and CVD Portal does not verify it. An app can use the name of a different app. The hosts come from the redirect URIs that the app registered. CVD Portal sends an authorization only to a registered redirect URI, so the hosts tell you which server got the access. Examine the hosts before you trust the name. The consent page shows the same host.

  • An app with more than one host shows all of them, for example "sends to a.example.com or b.example.com".
  • Two redirect URIs on one host show as one host.
  • A loopback redirect shows its scheme, for example http://127.0.0.1:8765. A native app redirect shows only its scheme, for example com.example.app:.

The revoke confirmation uses the same words.

The page shows only live authorizations. An authorization is live when one or more of its access or refresh tokens is not revoked and not expired. The date of authorization is the date of consent. It does not change when the app gets new tokens with its refresh token.

Click Revoke to end an authorization at once. The platform revokes all access and refresh tokens that the app holds for that user, and each authorization code that the app did not exchange yet. A token refresh or code exchange that is in progress at the same time also fails, so the app gets no new token. The app must ask for consent again to reconnect. Each revoke writes an OAUTH_TOKEN_REVOKED event to the audit log.

  • An admin can revoke each authorization in the organisation.
  • A member can revoke only the authorizations that the member gave.
  • Auditor, demo and partner drill-in sessions are read-only.

An app can also revoke its own tokens through the revocation endpoint.

Discovery metadata​

Clients can automatically discover endpoints and capabilities using standard metadata documents:

  • Authorization Server Metadata (RFC 8414): https://cvdportal.com/.well-known/oauth-authorization-server
  • Protected Resource Metadata (RFC 9728): https://cvdportal.com/.well-known/oauth-protected-resource

Environment variables​

The authorization server supports the following configuration options:

VariableDefaultDescription
OAUTH_ACCESS_TOKEN_TTL_SECONDS3600 (1 hour)Lifetime of issued access tokens in seconds
OAUTH_REFRESH_TOKEN_TTL_SECONDS2592000 (30 days)Lifetime of issued refresh tokens in seconds

Scope Taxonomy & Enforcement​

Every MCP tool and version 1 REST endpoint enforces a specific scope requirement.

Scopes​

ScopeGrantsEndpoints & Tools
readRead-only compliance data, submissions, products, and SBOMsGET /v1/compliance/*, GET /v1/products, GET /v1/sbom, GET /v1/vulnerabilities/*, GET /v1/jobs/*, POST /v1/jobs (classification), get_getting_started, get_compliance_status, generate_csaf_advisory, assess_response, get_next_deadline, get_next_task, get_job_status, submit_job, ask_expert, get_conformity_gaps, get_release_gate
submissions:writeSubmit vulnerability reportsPOST /v1/vulnerabilities, submit_vulnerability
submissions:triageTriage and progress vulnerability reportsMCP submission tools, see MCP Server
submissions:communicateDraft messages to reporters for approvaldraft_reporter_message
products:writeCreate and update productsPOST /v1/products
sbom:writePush SBOMs from CI/CDPOST /v1/sbom
sarif:writePush SARIF pipeline scan findingsPOST /v1/scan/sarif
gate:writeStore release gate verdicts from CI/CDPOST /v1/compliance/release-gate/runs
webhooks:writeRegister webhook callbacksPOST /v1/webhooks/register
gdpr:exportExport company data archiveGET /v1/gdpr/export
forge:submissions:readRead submissions for Jira syncGET /v1/forge/submissions
article14:preparePrepare Article 14 notification dossiersPOST /v1/jobs, submit_job
advisory:draftDraft CSAF and OpenVEX advisoriesPOST /v1/jobs, submit_job
conformity:preparePrepare conformity assessment & import prefillPOST /v1/jobs, submit_job

Enforcement rules​

  • OAuth tokens: Strictly limited to the consented scopes. Access to an endpoint or tool outside granted scopes returns HTTP 403 with an RFC 6750 challenge header: WWW-Authenticate: Bearer error="insufficient_scope", scope="<required>".
  • Registered scopes: An OAuth client gets only the scopes that it registered at /api/oauth/register. The platform removes each other requested scope at the authorization request, at consent, at code exchange and at refresh, and writes an OAUTH_SCOPE_DENIED audit event. A request that names no scope gets the registered scopes. A request with no registered scope gets the error invalid_scope. An OAuth token never has an empty scope list.
  • Legacy API keys: A legacy key with an empty scope list maintains unrestricted access for backward compatibility. A key with specified scopes enforces those scopes.
  • Plan gates: Scope checks and plan tier gates operate independently. A caller must hold the required scope and the company subscription must permit the feature.
  • ADMIN-only calls: POST /v1/webhooks/register, POST /v1/products, POST /v1/sbom, GET /v1/gdpr/export, POST /v1/compliance/assessments and every POST /v1/jobs type except CLASSIFY_PRODUCT need the ADMIN role, as in the dashboard. With an OAuth token, the platform reads the role of the token's user in the token's company at each call. That is the account role in the user's own company, or the membership role in a client company where the user holds a seat. A MEMBER gets HTTP 403 even when the token has the scope. A plain API key belongs to the company, so its scopes alone decide.

Human Approval Queue for Irreversible Actions​

Autonomous agents can prepare drafts and assemble packages, but cannot execute binding or outward-facing actions directly.

Every irreversible action initiated by an agent enqueues a PendingApproval record in the company workspace:

  • Article 14 stage filing: Marking early warnings or full notifications as filed on the ENISA Single Reporting Platform (SRP).
  • Security advisory publication: Transitioning draft advisories to published status.
  • Declaration of Conformity issuance: Issuing and signing the EU Declaration of Conformity (ISSUE_DOC). Agents assemble draft declarations, but cannot issue them directly.
  • Messages to reporters: Sending a message that an agent drafted for the vulnerability reporter (SEND_REPORTER_MESSAGE).

A company administrator reviews, approves, or rejects pending actions on the Agent Approval Queue page at /settings/approvals. The Settings menu does not show a link to this page. Open the URL directly. Approvals execute idempotently through the canonical execution path. Webhooks notify administrators when an approval request arrives (approval.requested).

Asynchronous Job Layer​

Long operations execute through background jobs to prevent HTTP timeout failures.

Operations​

The following operations run asynchronously through the job system:

  1. Product classification. Analyzes product description text against CRA Annex III and Annex IV categories (CLASSIFY_PRODUCT).
  2. Artifact drafting. Drafts CRA technical file artifacts using the assessment model chain (DRAFT_ARTIFACT).
  3. Import prefill. Extracts conformity model fields from product documentation with per-field provenance and confidence flags (IMPORT_PREFILL). Fields with confidence below threshold are flagged for human review.
  4. Server auto-enrichment. Closes enrichable gaps automatically from the fixed risk methodology and the stored SBOM (AUTO_ENRICH). The result lists candidate standards for the product category in candidateStandards. These are suggestions only. The job never records a standard as applied, and no CRA harmonised standard is cited in the Official Journal yet. The SBOM summary states the component count, how many components OSV.dev can check, and the date of the last complete check. It says so when the current SBOM was not checked. With no SBOM, the job writes no SBOM summary.
  5. Dossier assembly. Compiles the Annex VII technical documentation index and drafts the Annex V Declaration of Conformity (ASSEMBLE_DOSSIER). Does not issue the declaration.

Endpoints and tools​

InterfaceMethod / ToolDescription
RESTPOST /api/v1/jobsSubmit a new background job. Returns HTTP 202 with job ID.
RESTGET /api/v1/jobs/[id]Query current job status, progress, and result.
RESTGET /api/v1/compliance/gapsQuery structured conformity gaps and missing evidence.
MCPsubmit_jobEnqueue a new background job.
MCPget_job_statusQuery job status and result.
MCPget_conformity_gapsQuery structured conformity gaps across requirements.
MCPask_expertConsult the CRA regulatory expert assistant.

Idempotency​

Requests can include an idempotencyKey string. When a submit request carries an existing key for the company, the platform returns the existing job record without starting duplicate work.

Signed completion push​

Callers can provide an optional callbackUrl and callbackSecret. When the worker finishes processing the job, the server sends an HTTP POST request to the callback URL.

The push notification includes the following headers:

  • X-CVD-Timestamp: Time of the push event, in milliseconds since the Unix epoch.
  • X-CVD-Signature-256: Signature string in format t=<timestamp>,v1=<hmac-sha256>. The server sends this header only when you supply a callbackSecret.

The server validates all callback URLs against Server-Side Request Forgery rules before saving and before dispatch. The push delivery operates in non-blocking mode with exponential retry backoff.

Regulatory Expert Assistant (MCP ask_expert)​

Autonomous agents can consult the CRA regulatory expert tool (ask_expert) to resolve compliance ambiguities during the conformity run.

  • Scope: read.
  • Advisory nature: The tool provides advisory guidance only. It never claims that a Declaration of Conformity or Article 14 filing has occurred.
  • Grounded answers: Responses cite official European Union legal sources including Regulation (EU) 2024/2847 and applicable harmonized standards.
  • Structured needs: When the expert requires missing product or architecture details to answer definitively, it returns a structured need object with the target field, rationale, and question for human clarification.

Legacy Bearer API Keys​

Administrators can provision static API keys in the dashboard under Settings → API Keys. A general API key needs the Enterprise plan. Below Enterprise, you can create three kinds of limited key. A key with only the read scope needs the Reporting plan or higher, and works with the Slack app, the MCP server and the next-deadline and next-task endpoints. A key made only of MCP scopes and the gate:write scope needs the Compliance plan or higher. It works with the full MCP server and the two release gate endpoints. It does not open the rest of the REST API. A key with only forge: scopes works with the Jira app.

API keys are intended for server-to-server workflows and CI/CD integrations. Each key carries an optional scope list and an optional IP allowlist. Keys are transmitted via the standard HTTP header:

Authorization: Bearer <your-api-key>

Keys are stored as SHA-256 hashes. In the event of compromise, an administrator can immediately revoke or rotate the key in the dashboard.