Table of contents
  1. Authentication
  2. Credits & costs
  3. Create an audit
  4. Get an audit
  5. List audits
  6. Account
  7. Callbacks
  8. Errors & limits

Documentation

WebVitalist API

Order audits for client sites from your own tools, poll for results and receive a signed webhook when a report is ready. The API is included in the Agency plan; every order is paid with your plan's credits.

Base URL: https://webvitalist.harald-sigvartsen.workers.dev/api/v1

Authentication

Create keys under Account → API. Keys look like wv_live_ followed by 32 characters; the full key is shown once. Send it as a Bearer token on every request:

Authorization: Bearer wv_live_YOUR_KEY

Requests without a valid key receive 401. Revoking a key takes effect immediately. Keys are scoped to the account that created them and share its credits.

Credits & costs

The API never charges a card: each order debits credits from your subscription balance at the same rate as the web app. With too few credits the request fails with 402 and nothing is created.

productNameCreditsPagesTypical time
auditAudit11~3 min
deepDeep Audit315~6 min
fullFull Test825~12 min

Fix Pack is not orderable through the API. Agency includes 60 credits per month ($199/mo, or $1,990/yr with the year's credits up front). If an audit fails on our side the credits are refunded automatically.

Create an audit

POST /api/v1/audits

FieldTypeDescription
urlstring, requiredPublic http(s) page to audit. Scheme optional; credentials and fragments are stripped.
productaudit | deep | fullDefaults to audit.
callback_urlstring, optionalhttps URL on a public host. We POST a signed summary on completion or failure.
options.goalleads | sales | signups | traffic | otherSteers the AI copy and UX review.
options.notesstring ≤ 1000Free-form context for the consultant prompt.
options.platform_hintstring ≤ 60e.g. Shopify, WordPress, Next.js.
curl -X POST https://webvitalist.harald-sigvartsen.workers.dev/api/v1/audits \
  -H "Authorization: Bearer wv_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com",
    "product": "deep",
    "callback_url": "https://hooks.example.com/webvitalist",
    "options": { "goal": "sales", "platform_hint": "Shopify" }
  }'

Returns 201 with the new audit; processing starts immediately.

HTTP/1.1 201 Created
{
  "id": "3f9d1c2e-…",
  "status": "queued",
  "url": "https://example.com/",
  "product": "deep",
  "source": "api",
  "credits_spent": 3,
  "credits_remaining": 57,
  "scores": null,
  "created_at": "2026-09-13T12:00:00.000Z",
  "completed_at": null,
  "links": {
    "self": "https://webvitalist.harald-sigvartsen.workers.dev/api/v1/audits/3f9d1c2e-…",
    "live": "https://webvitalist.harald-sigvartsen.workers.dev/audits/3f9d1c2e-…?k=…",
    "report": null,
    "download": null
  }
}

Statuses are queued, processing, completed and failed. The links.live page can be shared with a client to watch progress; report and download are populated once completed.

Get an audit

GET /api/v1/audits/{id} — only audits owned by the key's account.

curl https://webvitalist.harald-sigvartsen.workers.dev/api/v1/audits/3f9d1c2e-… \
  -H "Authorization: Bearer wv_live_YOUR_KEY"
{
  "id": "3f9d1c2e-…",
  "status": "completed",
  "url": "https://example.com/",
  "product": "deep",
  "credits_spent": 3,
  "scores": { "overall": 71, "technical": 82, "performance": 54, "accessibility": 88, "content": 70, "ux": 66 },
  "progress": {
    "done": 8, "total": 8, "running": null,
    "steps": [ { "id": "fetch", "status": "done", "score": null, "summary": "…" }, … ]
  },
  "platform": { "cms": "Shopify", "framework": null, … },
  "result": { "pagesCrawled": 12, "crawl": { "brokenLinks": 2, … }, … },
  "error": null,
  "links": {
    "report": "…/report/3f9d1c2e-…?k=…",
    "download": "…/report/3f9d1c2e-…/download?k=…"
  }
}

progress.steps lists every step of the product in order with pending, running, done, skipped or failed. Poll every 10–20 seconds, or use a callback instead. The download link returns the full report as Markdown (text/markdown); it carries an access token, so treat it as a secret.

List audits

GET /api/v1/audits?limit=20&offset=0 — newest first, limit up to 100. The response has data, limit, offset and has_more. Includes audits ordered on the web and by monitors, not only API orders (see source).

curl "https://webvitalist.harald-sigvartsen.workers.dev/api/v1/audits?limit=20&offset=0" \
  -H "Authorization: Bearer wv_live_YOUR_KEY"

Account

GET /api/v1/me — email, plan (id, status, interval, period end), current credit balance, the key in use and the product price list. Handy for a “credits left” widget.

curl https://webvitalist.harald-sigvartsen.workers.dev/api/v1/me \
  -H "Authorization: Bearer wv_live_YOUR_KEY"

Callbacks

When an order with a callback_url completes or fails, we POST a JSON summary within seconds. Respond with any 2xx; on a non-2xx response or a timeout (10 s) we retry once. Deliveries carry a Webvitalist-Delivery header ({audit id}:{attempt}) so you can de-duplicate.

POST https://hooks.example.com/webvitalist
Content-Type: application/json
Webvitalist-Event: audit.completed
Webvitalist-Delivery: 3f9d1c2e-…:1
Webvitalist-Signature: sha256=9b3f…

{
  "id": "3f9d1c2e-…",
  "status": "completed",
  "url": "https://example.com/",
  "product": "deep",
  "scores": { "overall": 71, … },
  "report_url": "…/report/3f9d1c2e-…?k=…",
  "download_url": "…/report/3f9d1c2e-…/download?k=…",
  "error": null,
  "completed_at": "2026-09-13T12:06:41.000Z"
}

Verifying the signature

The Webvitalist-Signature header is sha256= followed by the hex HMAC-SHA256 of the raw request body. The HMAC secret is the hex SHA-256 of the API key that created the order — you can derive it from your key without storing anything extra. Always verify against the raw bytes, before parsing the JSON.

import { createHash, createHmac, timingSafeEqual } from 'node:crypto';

const secret = createHash('sha256').update(process.env.WEBVITALIST_KEY).digest('hex');

export function verify(rawBody, signatureHeader) {
  const expected = 'sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex');
  return expected.length === signatureHeader.length &&
    timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader));
}

Callback targets must be https on a public hostname. We follow no redirects. If a delivery fails twice the report is still available via GET /api/v1/audits/{id}.

Errors & limits

Errors share one shape:

{ "error": { "code": "insufficient_credits", "message": "…", "details": { "required": 3, "balance": 1 } } }
StatusCodeMeaning
400validation_errorMalformed JSON or invalid fields; `details.issues` lists each problem.
401unauthorizedMissing, malformed or revoked key, or the account has no Agency plan.
402insufficient_creditsNot enough credits for the product; `details.required` and `details.balance` are included.
404not_foundThe audit does not exist or belongs to another account.
415unsupported_media_typePOST bodies must be sent with `Content-Type: application/json`.
429rate_limitedMore than 10 create requests per minute per key. Back off and retry.

Creating audits is limited to 10 requests per minute per key; reads are not rate-limited beyond fair use. Request bodies are capped at 64 KB. Up to 10 active keys per account. Questions? Email hello@webvitalist.com.