Vraelis
How it worksPricingDevelopersResearchEnterprise
Sign inCheck your application

Developers

Put preflight in your pipeline.

Trigger a real-browser preflight from CI, gate the deploy on the decision, and pull structured evidence back. The decision is explainable, the evidence is deterministic, and every artifact stays private behind a short-lived signed URL.

queue a run against the preview build, then stop the release when a critical flow fails

Check your application →How it works

The CI gate

Stop the deploy when a critical flow fails.

When your preview build goes live, submit the claim, approve the plan Vraelis derives from it, then run that approved plan and ship only when the decision is Verified. Failed and Blocked stop the release; a run that merely finished is not a pass. Approval is a separate step on purpose, so a first submit prepares a plan rather than spending on one nobody read.

Not available yetThis workflow does not run today. It is here to show the shape we are building toward. Do not copy it into a pipeline; it will fail.
1 GitHub Action
yaml
# .github/workflows/verify.yml
name: Vraelis Verification
on: [deployment_status]
jobs:
  verify:
    if: github.event.deployment_status.state == 'success'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 20 }
      - run: node scripts/verify-gate.mjs
        env:
          VRAELIS_API_KEY: ${{ secrets.VRAELIS_API_KEY }}
          VRAELIS_CLAIM: ${{ vars.VRAELIS_CLAIM }}
          PREVIEW_URL: ${{ github.event.deployment_status.target_url }}
          # The plan a person approved for this claim. Until it is set, the job stops at step 1.
          VRAELIS_REVIEWED_PLAN_ID: ${{ vars.VRAELIS_REVIEWED_PLAN_ID }}
2 Gate script
node
// scripts/verify-gate.mjs -- ship only when the deployed build keeps its promise.
// 0 verified   1 failed   2 blocked   3 no decision reached   4 the plan still needs a person to approve it
import { randomUUID } from "node:crypto";

const API = "https://vraelis.com/api/v1/verifications";
const headers = {
  "content-type": "application/json",
  "x-api-key": process.env.VRAELIS_API_KEY,
  "idempotency-key": randomUUID(),
};
const request = { deployment_url: process.env.PREVIEW_URL, claim: process.env.VRAELIS_CLAIM };
const post = (body) => fetch(API, { method: "POST", headers, body: JSON.stringify(body) });

// 1. Submit the claim. With no reviewed_plan_id this answers 202 "review_required": Vraelis derived a plan,
//    nothing ran, nothing was charged, and there is NO verification_id yet, only a reviewed plan to approve.
const planId = process.env.VRAELIS_REVIEWED_PLAN_ID;
if (!planId) {
  const prepared = await post(request);
  if (!prepared.ok) { console.error("Vraelis request failed: " + prepared.status); process.exit(3); }
  const plan = await prepared.json();
  console.error("A person has to approve this plan before it can run: " + plan.reviewed_plan_id);
  (plan.requirements || []).forEach((r) => console.error("  " + r));
  process.exit(4); // nothing ran, so this is not a verdict and must not be read as one
}

// 2. Approval happens outside this script: the Review queue in the console, or
//    POST /v1/verifications/plans/{id}/approve. Holding the id is not approval.

// 3. Resubmit the SAME deployment and claim with the approved plan. This runs exactly what was reviewed,
//    and it is the first response that carries a verification_id.
const started = await post({ ...request, reviewed_plan_id: planId });
if (!started.ok) { console.error("Vraelis refused the run: " + started.status); process.exit(3); }
const { verification_id } = await started.json(); // this response also carries human_reviewed: true

// 4. Poll until a DECISION lands. While the run is going the body is just the id and the state.
let decision = null, out;
for (let i = 0; i < 120 && decision === null; i++) {
  await new Promise((r) => setTimeout(r, 5000));
  const res = await fetch(API + "/" + verification_id, { headers });
  if (!res.ok) { console.error("Vraelis request failed: " + res.status); process.exit(3); }
  out = await res.json();
  decision = out.decision ?? null; // "verified" | "failed" | "blocked", absent while still running
}

// 5. Gate on the DECISION, never the run state. A finished run is not a pass; only "verified" ships.
switch (decision) {
  case "verified": console.log("Verified"); process.exit(0);
  case "failed": console.error("Failed: the claim did not hold"); (out.failures || []).forEach((f) => console.error("  " + f.title)); process.exit(1);
  case "blocked": console.error("Blocked: no verdict was reached"); process.exit(2);
  default: console.error("No decision within the polling window"); process.exit(3);
}

The request and response shapes above are copied from the shipped route, POST /api/v1/verifications. Running the whole path from a CI runner is what is still opening, which is why the notice above stands.

The verification, as data

One run, one structured decision.

Every run returns the same shape, whether you read it in the app, over the API, or in CI. It is built for a machine to act on, not a human to interpret.

The decision
verified, failed, or blocked, from one explainable rule, never a numeric score.
Per-flow results
Each approved flow with its pass or fail state and the step where it broke.
Issues with repro
Each critical failure with its requirement, expected and observed behavior, and exact reproduction steps.
The step record
Every step as the browser ran it: the URL, the response status, what was expected, what was observed, and how long it took.
Private artifacts
Screenshots live in a private bucket, reached only by a short-lived signed URL.
Console and network
Console errors and failed network requests captured during the run, alongside the steps that produced them.
A repair prompt
On a failure, the fix written as a prompt for a coding agent. Rerunning after the fix is something you start; nothing re-verifies on its own.

Private by construction

Evidence stays scoped to your account.

Screenshots live in a private bucket. There is no public URL. A request for an artifact is owner-checked against the run, then answered with a short-lived signed URL that expires in minutes. The rest of the evidence, the step record and the console and network activity, is read through the same owner-checked report. API keys are server-side secrets, shown once and stored only as a hash.

You choose what a run can reach. Point it at a preview or staging deployment and keep Stripe in test mode. Vraelis drives your app from the outside, so a run can touch whatever that environment touches.

Deterministic and explainable

A gate you can trust.

The pass or fail of every flow is what the browser actually did, not a model's opinion. The decision follows one rule you can read: any critical flow that fails means the verification is Failed. When Vraelis suggests a cause, it is labeled as interpretation and never counts as the gate.

See the decision rule →

Where we are

Honest about what is live.

The run and report data plane is real: a Postgres-backed queue, a worker that drives an isolated browser, and private, owner-scoped evidence. API-key triggers and CI gating are opening in early access, and the deeper connections for GitHub, Vercel, Supabase, and Stripe build out from there.

We will not document an endpoint we have not shipped. When a surface is live, it appears in the signed-in console with a real example you can run.

Check your applicationEnterprise and security
Vraelis

Verifies software built with AI actually works. Name the outcome that must hold, and Vraelis checks the live result and keeps the evidence.

Product
How it worksLimitationsPricingEnterpriseCheck your application
Developers
Developer overviewCLI and CIResearchAPI & webhooks
Account
DashboardAccountBillingSign in
Legal
Enterprise & securityPrivacyTermsRefundsData rightsSubprocessorsTrademarkContact
© 2026 Vraelis. All rights reserved.Questions? Contact us