Also as Markdown for agents: /developers/design.md
NOVAI Design Review API
This guide is for AI agents and the developers who build them.
You send the API a website. It answers with three things:
- how the design measures today;
- what to fix, ranked;
- a modern direction from the NOVAI design library: colour tokens, fonts and component CSS.
Use it to make a site look current, crafted and fresh, and to check that your changes worked.
It works like the NOVAI SEO API (Site Validation). The loop is review, fix, then review again with previous_review_id to get a diff.
- Code:
app/design_review.py - Tests:
tests/test_v229.py - Live docs: https://novaipulse.com/developers/design
1. Quick facts
| Base URL | https://novaipulse.com |
|---|---|
| For agents with a key | POST /api/v1/design/reviews, GET /api/v1/design/reviews/{review_id} |
| For agents without a key | MCP tool review_design on https://novaipulse.com/mcp (a summary, rate limited) |
| For NOVAI systems | POST /api/pulse/design/reviews, GET /api/pulse/design/reviews/{id}, GET /api/pulse/design/capabilities (PULSE_API_KEY) |
| Auth (agents) | Authorization: Bearer npk_... or X-API-Key: npk_.... Get a key at https://novaipulse.com/developers#pricing |
| Cost | One review counts as one scan of your plan: Developer 500 a month, Pro 5,000 a month |
| Limits | 60 design reviews an hour per key. The MCP tool: 12 per 10 minutes per IP, and a site reviewed in the last day is answered from that review |
| Speed | 1 to 5 seconds. The answer is synchronous; there is nothing to poll |
| Format | JSON in, JSON out |
2. What it measures
NOVAI reads the start page and its stylesheets:
- inline styles;
- the site's own CSS;
- CSS from the common font and CSS hosts (Google Fonts, Typekit, jsDelivr, cdnjs, unpkg, Bootstrap CDN, Bunny Fonts).
It reads them like a browser, through the same SSRF guard as the crawler: public addresses only, the DNS answer pinned and every redirect checked. It never runs the site's scripts and never logs in.
There are five areas. Each is scored 0 to 100. The overall score is the weighted mean.
| Area | Weight | What it checks |
|---|---|---|
typography | 25 % | A chosen typeface or a 2005 system font; novelty fonts (Comic Sans, Papyrus, Courier); more than three families; body size under 16px; leading under 1.4; a timid, fixed headline scale |
color | 20 % | Body text contrast under 4.5:1 (WCAG 2.2 1.4.3); pure black on pure white; more than 40 one-off colours; several fully saturated colours competing; no colour tokens |
layout | 25 % | Not set up for phones; no responsive rules; table layouts and legacy tags; fixed pixel widths; float columns; more than 8 menu links; no call-to-action button; no headline |
modernity | 15 % | Bootstrap 2/3, jQuery 1.x or a 2010 to 2015 WordPress theme; many inline styles; only JPEG/PNG; images without width and height; no favicon; no share image |
polish | 15 % | Keyboard focus hidden; no interaction motion at all; motion that ignores reduced-motion; no dark mode; no shape or depth system |
The overall score maps to a grade:
fresh: 85 and upgood: 70 to 84dated: 50 to 69outdated: under 50
The review also returns two hints:
era: roughly when the design's code is from (before 2014,2014 to 2018,2019 to 2022,2023 or later). It is read from what the code uses, and it is a hint, not a date.style_now: which NOVAI style family the current look is closest to.
3. Review a site
POST /api/v1/design/reviews
Authorization: Bearer npk_...
Content-Type: application/json
{
"url": "https://andersson-ror.se", // required, a public http(s) address or a bare domain
"industry": "rörmokare", // optional; otherwise read from the page's title and description
"brand_color": "#1b4f63", // optional; otherwise the site's own brand colour, when it has one
"family": "", // optional: force a style family (see §5)
"previous_review_id": "", // optional: the review you fixed, for a diff
"direction": true // false: skip the direction (smaller answer)
}
The answer looks like this (shortened):
{
"version": "novai-design-review-1",
"review_id": "dr_7c1e0a...",
"url": "https://andersson-ror.se/",
"status": "done",
"score": {"overall": 42, "grade": "outdated",
"areas": {"typography": 52, "color": 55, "layout": 0, "modernity": 52, "polish": 67}},
"era": {"label": "before 2014", "why": "table layout, legacy HTML tags or plugins"},
"style_now": "none",
"confidence": "high", "confidence_note": "",
"summary": "Design score 42/100 (outdated); the code looks before 2014. Fix first: ...",
"measured": { "fonts": ["arial"], "body_size_px": 12, "text_contrast": 2.85, "grid": 0, "...": "..." },
"fixes": [ { "priority": 1, "code": "LAYOUT_LEGACY_MARKUP", "...": "..." } ],
"direction": { "family": "crisp", "palette": { "...": "..." }, "fonts": { "...": "..." },
"tokens_css": ":root{--bg:...}", "components_css": "...", "...": "..." },
"seconds": 2.1
}
confidence is high, medium or low, with confidence_note. It says how much of the CSS could be read. With little CSS (styles on another host, or drawn by JavaScript after loading), typography and colour are partly unmeasured and their scores say less.
status is done or unreadable. An unreadable review returns error (for example start_page_unreadable) and http_status.
Read a stored review again with GET /api/v1/design/reviews/{review_id}. You can only read your own key's reviews.
4. A fix
Every fix has the same shape. Do them in priority order.
| Field | What it is |
|---|---|
priority | 1 is first. The order is severity, then expected points |
code | The check, for example TYPE_DEFAULT_FONT, COLOR_LOW_CONTRAST, LAYOUT_FIXED_WIDTH |
area | typography, color, layout, modernity or polish |
severity | critical, high, medium or low |
title, why, how | What is wrong, why it matters to visitors, and the steps to fix it |
css_example | Where it helps: CSS that fixes it, written in the NOVAI design tokens (--bg, --fg, --brand, --font-display, --r-m ...) |
evidence | What NOVAI measured, for example {"body_size_px": 12} |
expected_points | Our estimate of how much the overall score rises when the fix is done |
effort | low, medium or high |
source | The standard behind it, when there is one (WCAG 2.2, Core Web Vitals) |
verify | How the next review decides that it is fixed |
All the codes are listed in GET /api/pulse/design/capabilities (checks). That list is built from the code, so it is always current.
5. The direction (what makes it "cool, modern and fresh")
The direction is not text from a language model. It is the recipe the NOVAI design library composes for this business, the same composer that designs every site NOVAI builds (app/design/compose.py).
What the library holds:
- over 1,100 variants;
- 10 style families;
- an OKLCH colour engine that guarantees WCAG 2.2 AA;
- 73 font pairings of self-hosted fonts;
- 500 art-directed templates with industry playbooks.
How the recipe is built:
- Industry. The business's industry selects the moods, the style-family weights and the colour schemes.
- Brand colour. The site's own brand colour, or the one you send, becomes the palette. When there is none, a curated library palette is used.
- The rest. The composer then picks one variant for every element.
| Field | What it is |
|---|---|
family, family_description | The recommended style family: soft, crisp, editorial, bold, brutal, organic, luxe, tech, playful or glass |
alternatives | Two other families that fit the industry |
moods | What the design should express, for example trustworthy, friendly, modern |
palette | brand, accent, scheme (light, paper, mist, dark ...), from_site (true when built from the site's own colour) and library_palette (the curated palette's id otherwise) |
fonts | The pairing: display and body family names, pairing id |
tokens_css | One :root{...} block ready to paste: every colour token (every text and background pair meets AA, body text 7:1), the type scale (--fs-h1 fluid with clamp), radius, shadow, spacing and motion tokens |
components_css | The library's CSS for buttons, cards, headings, focus rings, links and badges in this recipe. It styles the class names in §6 and uses only the tokens |
components | Each picked library variant (hero, header, services, call to action, footer, button, card ...) with its id, name and notes, so an agent can describe or rebuild the layout |
section_order, rhythm | The home page's section order, and how section backgrounds alternate |
templates | Three of the 500 art-directed templates for this industry (hero layout, navigation, motion, tone, sections, palette) for inspiration |
playbook | The industry's design objective, the proof that persuades, the page order and what to avoid |
motion | Three low-cost motion ideas with how to replace them under reduced motion |
novai_recipe | The recipe itself (family, slots, palette, fingerprint), in the format NOVAI's site builder stores |
To try another look, send family (one of the ten). The recipe is then composed within that family.
6. Applying the direction to a site
tokens_css and components_css style a small set of shared class names. Map your markup to them, or rename the selectors to yours.
| Class | Element |
|---|---|
a.btn.btn-primary, a.btn.btn-secondary, a.btn.btn-ghost | Buttons, at least 44px high, with an optional <span class="btn-ico">→</span> |
article.card with span.ico, h3, p | Cards |
.h-display (h1), .h-title (section h2), .h-sub | Headings |
.eyebrow, .chip, .badge | Small labels |
.prose a, .tlink | Text links |
:focus-visible | The focus ring |
A safe order for an agent:
- Paste
tokens_cssand load the two fonts as self-hosted WOFF2, withfont-display: swap. - Point
bodyatvar(--font-body),var(--fg)andvar(--bg), and headings atvar(--font-display). - Fix the
criticalandhighfixes, using theircss_example. - Add
components_cssand use the classes for buttons, cards and headings. - Review again with
previous_review_id.
7. Verify: the diff
Send the next review with "previous_review_id": "<the review you fixed>". The answer then has:
"diff": {"fixed": ["TYPE_DEFAULT_FONT", "COLOR_LOW_CONTRAST"], "still_open": ["LAYOUT_FLOATS"],
"new": [], "score_before": 42, "score_now": 71, "change": 29}
The previous review must be your own and of the same site. Otherwise no diff is returned.
8. For agents without a key: the MCP tool
The tool is review_design on the public MCP server (https://novaipulse.com/mcp, JSON-RPC 2.0, Streamable HTTP). It is also listed in /.well-known/mcp.json and /.well-known/agent.json.
{"jsonrpc": "2.0", "id": 1, "method": "tools/call",
"params": {"name": "review_design", "arguments": {"url": "andersson-ror.se", "industry": "rörmokare"}}}
It answers with:
- the score and the era;
- the summary;
- the number of fixes and the top 3;
- the direction's family, palette and fonts.
It does not include the tokens, the component CSS or the full fix list. Those come from the keyed API.
9. Errors
| Status | error | Meaning |
|---|---|---|
| 400 | bad_url | Not a public http(s) address |
| 401 | missing_key, bad_key | No key, or not a valid one |
| 402 | plan_inactive | The payment failed or the plan was cancelled |
| 404 | review_not_found | No design review with that id for this key |
| 429 | quota_exceeded, rate_limited | Monthly quota used, or more than 60 reviews this hour |
10. Examples
curl
curl -s -X POST https://novaipulse.com/api/v1/design/reviews \
-H "Authorization: Bearer $NOVAI_API_KEY" -H "Content-Type: application/json" \
-d '{"url": "https://yoursite.com", "industry": "dental clinic"}' | jq '.score, .fixes[0], .direction.fonts'
JavaScript
const r = await fetch("https://novaipulse.com/api/v1/design/reviews", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.NOVAI_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ url: "https://yoursite.com" }),
});
const review = await r.json();
// review.direction.tokens_css -> paste into your global stylesheet
// review.fixes -> work through them in priority order
An agent's loop
1. review = POST /api/v1/design/reviews {url}
2. apply review.direction.tokens_css and the fonts
3. for fix in review.fixes where severity in (critical, high): apply fix.css_example / fix.how
4. deploy
5. again = POST /api/v1/design/reviews {url, previous_review_id: review.review_id}
6. stop when again.score.grade == "fresh", or again.diff.still_open is empty
11. How NOVAI itself uses it
The design is optimised the same way as SEO, through the same API.
- Every site we host or preview (generated sites, Reborn Online copies, new designs) is reviewed once a month. This is the
design-reviewloop on /admin/ops, on the same site list as the speed tests. - Early warning. A site of ours under 75 raises an early warning in ops.
- The leads' own sites are reviewed after ours. The review goes to Clarity in the lead payload (
design: score, era, top fixes, direction), so sellers can show what a modern version would change. - The SEO API's full runs (
/api/pulse/validate/runswithdepth: full) include adesignblock. A system that already talks to the SEO API gets design in the same conversation. - The developer page (
/developers) shows the design score in the free scan, next to the SEO issues. - The direction comes from the same composer and the same inputs (industry, the site's own brand colour) that NOVAI's site builder uses. It shows the kind of design a NOVAI-built site gets.
12. Limits and honest notes
- The review reads code and CSS. It does not judge photos, illustrations or copywriting, and it does not take a screenshot.
- Sites that draw everything with JavaScript after loading show less in their HTML. The review then says less, and never more than it saw.
- Stylesheets on other hosts than the common font and CSS hosts are not read (they are listed in
measured.sheetswithread: false). - The
erais a hint from the code, not the site's real age. - The direction is a starting point from our library. A human designer, or the business's own taste, may choose another family. Send
familyto see it.