API reference

One base URL, Bearer auth, JSON in and out. Base URL: https://vibe-val.com

Authentication

Every API request needs an API key in the Authorization header. Keys start with csa_ and are issued from your dashboard. The full key is shown once at creation. We store only a SHA-256 hash.

Authorization: Bearer csa_your_key_here

Revoke a key any time from the dashboard. Revoked keys fail immediately with 401 invalid_key.

Validate a change

POST/v1/validate

Submit a code diff and its context. The deterministic rule engine checks for the artifacts IEC 62304 requires at your risk class, then Claude writes a CSA-style rationale. The verdict comes from the rules, never the LLM.

Request body

FieldTypeNotes
frameworkstringMust be "iec-62304"
risk_classstring"A", "B" or "C"
change_typestring"new", "modification" or "retirement"
diffstringThe unified diff. Non-empty, max 1 MB
contextstringWhat changed and why. Minimum 10 characters

Cost per check

Risk classCost
Class A$0.50
Class B$2.00
Class C$5.00

Credits are deducted atomically before the check runs. If the balance is short, you get a 402 and nothing is charged.

Response 200

{
  "id": "8f14e45f-ceea-4e4b-9a5c-9c2f0a1b2c3d",
  "compliant": false,
  "findings": [
    {
      "rule_id": "iec62304-B-test_evidence",
      "severity": "major",
      "message": "Missing required artifact: test evidence",
      "requirement": "IEC 62304 §5.5-5.7 (Class B)"
    }
  ],
  "risk_assessment": {
    "risk_class": "B",
    "required_artifacts": ["change_description", "test_evidence", "design_reference"],
    "missing_artifacts": ["test_evidence"]
  },
  "attestation": {
    "timestamp": "2026-06-11T14:02:11.000Z",
    "input_hash": "sha256:...",
    "framework_version": "iec-62304",
    "engine_version": "1.0.0"
  },
  "rationale_status": "ready",
  "remediation": [
    "Add test files or reference existing test coverage in the diff"
  ]
}

Finding severity is critical for Class C gaps, major for Class A and B. File attestation.input_hash in your Design History File: the same inputs always produce the same hash, so the record is independently verifiable.

Fetch the rationale

GET/v1/validate/{id}/rationale

Returns the LLM-generated CSA rationale for a validation. In practice rationale_status is already "ready" when /v1/validate responds, so one call is usually enough.

StatusMeaning
200Rationale ready: { id, status: "ready", rationale }. If generation failed, status is "unavailable" — the verdict and attestation still stand
202Still generating: { id, status: "pending", eta_seconds }. Poll again
404Unknown validation ID

Check your balance

GET/v1/credits/balance

{
  "balance_cents": 700,
  "balance_dollars": "7.00",
  "cost_per_check": { "A": 50, "B": 200, "C": 500 },
  "estimated_remaining": { "class_a": 14, "class_b": 3, "class_c": 1 }
}

Buy credits

POST/v1/checkout/credits

Creates a Stripe Checkout session for a credit pack and returns the payment URL. Most people just use the buy page, but the endpoint is there if you want to wire purchasing into your own tooling.

FieldNotes
packOne of VIBEVAL20, VIBEVAL50, VIBEVAL100, VIBEVAL250, VIBEVAL500

Response: { "url": "https://checkout.stripe.com/...", "session_id": "cs_..." }. Credits land on your account when Stripe confirms payment, usually within seconds. They never expire.

Required artifacts by risk class

The rule engine checks the diff and context for structural evidence of each artifact your risk class requires.

ArtifactABCWhat the engine looks for
change_descriptionA meaningful context field (10+ characters)
test_evidenceTest files, test directories, or test framework calls in the diff (.test.ts, describe(, assert, pytest, @Test...)
design_referenceTraceability IDs or design references (REQ-001, SRS-12, DHF, "requirement", "traceability")
risk_analysisRisk analysis markers (FMEA, "hazard", "severity", "mitigation", "risk analysis")
formal_verificationVerification evidence ("static analysis", "coverage report", MC/DC, "proof", "model check")

Error codes

Errors are JSON: { "error": "...", "code": "...", "details": "..." }.

HTTPCodeMeaning
400invalid_bodyRequest body is not a JSON object
400invalid_frameworkOnly iec-62304 is supported
400invalid_risk_classrisk_class must be A, B or C
400invalid_change_typeMust be new, modification or retirement
400invalid_diffdiff is missing or empty
400diff_too_largediff exceeds 1 MB
400invalid_contextcontext is missing or under 10 characters
400invalid_packUnknown credit pack name
401missing_authNo Authorization header
401invalid_key_formatKey doesn't start with csa_
401invalid_keyKey is unknown or revoked
402insufficient_creditsBalance too low. Response includes cost_cents, balance_cents and buy_url
404not_foundValidation ID doesn't exist
405Wrong HTTP method for the endpoint
502stripe_errorStripe rejected the checkout request

Code examples

curl

curl -s https://vibe-val.com/v1/validate \
  -H "Authorization: Bearer $VIBEVAL_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "framework": "iec-62304",
    "risk_class": "B",
    "change_type": "modification",
    "diff": "--- a/src/dose.ts\n+++ b/src/dose.ts\n+ // REQ-042: clamp dose\n+ const dose = Math.min(input, MAX_DOSE);\n+ expect(clampDose(99)).toBe(MAX_DOSE);",
    "context": "Clamp dose input to MAX_DOSE per REQ-042. Unit test added."
  }'

Python

import os
import requests

resp = requests.post(
    "https://vibe-val.com/v1/validate",
    headers={"Authorization": f"Bearer {os.environ['VIBEVAL_KEY']}"},
    json={
        "framework": "iec-62304",
        "risk_class": "B",
        "change_type": "modification",
        "diff": open("change.diff").read(),
        "context": "Clamp dose input to MAX_DOSE per REQ-042. Unit test added.",
    },
)
result = resp.json()

if resp.status_code == 402:
    raise SystemExit(f"Out of credits: {result['details']}")
resp.raise_for_status()

print("Compliant:" , result["compliant"])
print("Attestation:", result["attestation"]["input_hash"])

rationale = requests.get(
    f"https://vibe-val.com/v1/validate/{result['id']}/rationale",
    headers={"Authorization": f"Bearer {os.environ['VIBEVAL_KEY']}"},
).json()
print(rationale.get("rationale", "rationale unavailable"))

TypeScript

const BASE = "https://vibe-val.com";
const headers = {
  Authorization: `Bearer ${process.env.VIBEVAL_KEY}`,
  "Content-Type": "application/json",
};

const res = await fetch(`${BASE}/v1/validate`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    framework: "iec-62304",
    risk_class: "B",
    change_type: "modification",
    diff: await fs.readFile("change.diff", "utf8"),
    context: "Clamp dose input to MAX_DOSE per REQ-042. Unit test added.",
  }),
});

if (res.status === 402) {
  const { details } = await res.json();
  throw new Error(`Out of credits: ${details}`);
}

const result = await res.json();
console.log(result.compliant, result.attestation.input_hash);

const rationale = await fetch(
  `${BASE}/v1/validate/${result.id}/rationale`,
  { headers },
).then((r) => r.json());
console.log(rationale.rationale ?? "rationale unavailable");
Want a worked example without spending credits? GET /v1/validate/example returns a complete fixture response, no auth required. The result viewer renders it.