NOVAI for developers

NOVAI Pulse API

Scan a public website and get a ranked list of fixes for search (SEO), AI answers (GEO), AI agents and security. Base URL https://novaipulse.com/api/v1. JSON in, JSON out.

Authentication

Send your key in a header: Authorization: Bearer npk_... or X-API-Key: npk_.... Keep it on the server or in CI secrets, never in browser code. Get a key.

Start a scan

POST /api/v1/scans
{
  "url": "https://yoursite.com",       // required, a public http(s) address
  "depth": "quick",                    // "quick" (default) or "full"
  "previous_scan_id": "",              // optional: the scan you fixed, for a diff
  "site_id": "",                       // optional: your own id for the site
  "callback_url": ""                   // optional: https URL we POST to when done
}

Answers 202 with scan_id, status_url and poll_after_seconds.

Read the result

GET /api/v1/scans/{scan_id}

While status is queued or running, poll again after poll_after_seconds. When it is done you get score (overall and per area), fixes, advice, next_step, a ready resubmit body and, on a re-scan, diff (fixed, still open, regressed, new).

A fix

FieldWhat it is
fix_idStable id for this issue on this site, the same across re-scans.
priority1 is the fix to do first (impact for effort).
codeThe check, for example DEV_SECRET_IN_CLIENT or missing_canonical.
channelseo, geo, agentic, security, access or compliance.
severitycritical, high, medium, low or info.
title, why, howWhat is wrong, why it matters, the steps to fix it.
affected_urls, evidenceWhere we saw it and what we measured.
expected_points, effortOur estimate of the score gain, and low, medium or high effort.
example_patchWhen we can: a draft of the file or markup, built from the site's own content.
verifyHow the next scan decides that the fix is done.

Tell us what you did

POST /api/v1/scans/{scan_id}/feedback
{"items": [{"fix_id": "...", "status": "applied"},
           {"fix_id": "...", "status": "not_applicable", "note": "We have no physical address"}]}

Status is applied, rejected, not_applicable or cannot (the last three need a note). Fixes you rule out drop to the bottom on later scans. Then deploy and POST a new scan with previous_scan_id to verify.

Usage, lessons and billing

GET  /api/v1/usage            plan, scans used and left this month, when it resets
GET  /api/v1/lessons          which fixes moved the score on your sites
POST /api/v1/billing-portal   a link to change plan, card or cancel

Limits

PlanScans a monthPrice
Developer500$19 or €19 a month
Pro5,000$79 or €79 a month

On every plan: at most 30 scans an hour per key and 10 an hour for one site. A refused request answers 429 with Retry-After.

Errors

StatuserrorMeaning
400bad_urlNot a public http(s) address.
401missing_key, bad_keyNo key, or not a valid one.
402plan_inactiveThe payment failed or the plan was cancelled.
404scan_not_foundNo scan with that id for this key.
429quota_exceeded, rate_limitedMonthly quota used, or too many scans this hour.
503busyMany scans right now. Retry after the given seconds.

Webhook

With callback_url we POST {"event": "validation.run.finished", "scan_id": "...", "status": "done", "status_url": "/api/v1/scans/..."} when the scan ends. Treat it as a nudge: read the result from status_url with your key.

Example: fail CI on a critical issue

ID=$(curl -s -X POST https://novaipulse.com/api/v1/scans -H "Authorization: Bearer $NOVAI_API_KEY" \
  -H "Content-Type: application/json" -d "{\"url\": \"$PREVIEW_URL\"}" | jq -r .scan_id)
until [ "$(curl -s https://novaipulse.com/api/v1/scans/$ID -H "Authorization: Bearer $NOVAI_API_KEY" | jq -r .status)" = done ]; do sleep 10; done
curl -s https://novaipulse.com/api/v1/scans/$ID -H "Authorization: Bearer $NOVAI_API_KEY" \
  | jq -e '[.fixes[] | select(.severity == "critical")] | length == 0'

Example: JavaScript

const res = await fetch("https://novaipulse.com/api/v1/scans", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.NOVAI_API_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({ url: "https://yoursite.com" }),
});
const { scan_id, poll_after_seconds } = await res.json();