Privacy

Last updated 4 August 2026

Using LoupeKit needs no account. The identifier the product runs on is a random UUID your browser generates on first run and keeps locally — nothing to sign up for, no email, no password. That covers every feature except one.

The exception is team sharing, and it is opt-in. Handing a palette to a colleague means the product has to know who the colleague is, so that feature asks for an email address — the only personal data in the whole system. If you never turn it on, none of it applies to you and no address is ever stored.

This page describes every byte that leaves your browser, field by field. It is written to be checked against the code, not taken on trust.

Running an audit

A Vibe Audit is the only feature that sends anything to us, and it runs only when you press the button. It sends the payload below to https://api.loupekit.comand nothing else.

Identity

domain
Hostname only — example.com, never the path or query
deviceId
A random UUID this browser minted. Not tied to a person

stats.dom — integers

totalDomNodes
How many elements the page has
inlineSvgCount
Inline <svg> elements
maxNestingDepth
Deepest element nesting
deepNestingCount
Elements past the depth threshold
stockRadixCount
Unstyled Radix/shadcn data attributes
semanticElementCount
header, nav, main, section, article, footer
emptyWrapperCount
Bare <div>s wrapping exactly one child
duplicateBlockCount
Repeated subtree shapes — tag names only, never text
textLength
How many characters of visible text the page has — the count, never the text
markupLength
How many characters of markup surround it

stats.css

longTailwindCount
Elements carrying a long utility string
maxUtilityClassCount
The longest such string, as a count
arbitraryValueCount
Uses of Tailwind arbitrary values
stockColorClasses
Class names such as bg-slate-50 — names, not content
hasCustomProperties
Whether the page declares CSS variables
distinctSpacingValues
How many different padding, margin and gap values are painted
declaredClassCount
Class names in the stylesheets this browser could read
unusedClassCount
How many of those match nothing on the page
readableStylesheets
How many stylesheets were readable at all
distinctRadiusValues
How many different corner radii are painted
maxShadesPerHue
Most shades of one hue — a count, never a colour value
inlineStyleCount
Elements carrying a style attribute
classedElementCount
Elements carrying a class attribute

stats.js

aiBuilderGlobals
Names of generator globals found on window
heavyLibraries
Names of large libraries detected
totalScriptCount
How many script tags

stats.copy — the only free text

pageTitle
The <title>, truncated and redacted
h1Texts
Heading text, truncated and redacted
h2Texts
Heading text, truncated and redacted
hasCustomFavicon
Whether a favicon is declared
placeholderImageCount
Images pointing at a placeholder service
wideTextBlocks
Paragraphs estimated to run past a comfortable line length
textBlockCount
How many paragraphs were long enough to measure

stats.a11y — integers

imageCount
How many images the page has
imagesMissingAlt
How many carry no alt attribute
controlCount
Buttons and links
unnamedControls
How many have no readable name
inputCount
Form fields
unlabelledInputs
How many have no label of any kind

Headings are the only free text in the payload. Before they leave the page they are stripped of street addresses and long identifier runs, then truncated — in that order, because truncating first can leave a recognisable fragment that the pattern no longer matches. The server truncates everything a second time, on the principle that a script running inside someone else's page is not a trustworthy place to enforce a limit.

What is never sent

What is stored afterwards

Two rows. One in a users table, keyed by your device id, holding your remaining free audits and — if you buy Pro — your plan and Stripe customer id. One in an aggregate table recording the hostname, the score and the verdict.

The aggregate row carries no device id. Not omitted by policy — the table has no column for one. Joining a device to the domains it audited would reconstruct browsing history, which is the one thing this extension exists to avoid doing.

Abuse limits

The API counts requests per network address so a script cannot mint device ids in a loop and exhaust the service. The counter belongs to Cloudflare, resets within a minute, and holds a number — your address is never written to our database, never logged, and never joined to an audit, a domain or a device id. Your browser reveals its address to every server it contacts, including this one; the limit reads it and stores nothing.

Vulnerability dating

On the Stack tab, Pro users can press a button that checks the detected versions against published security advisories. It is the only part of that tab that leaves your browser, it only ever runs when you press it, and it is listed here on its own because it is a second request to us — the audit is not the only one any more.

A result says a version has a published advisory against it. It does not say the page can be attacked through it — that depends on how the library is used, which nothing outside the browser can see. Silence is equally narrow: versions we could not read, or could not resolve to a package, are reported as unchecked rather than counted as clean.

Everything else stays local

Stack detection, the colour palette, the font matrix, the asset inventory, the box model, live element edits, the class optimizer, the copy sanitizer and the responsive tester all run entirely inside your browser. None of them sends anything to us on its own. Results leave when you copy them or save the asset archive — both built on this device — and, if you have signed in, when you press Share on a palette or an environment. That third one is the only route by which anything extracted reaches a server, it needs an explicit press every time, and what it sends is described under "Accounts and team sharing" below.

Four of these keep something between sessions, and all four keep it here. Your audit history is the last twenty scores, with the domain, the verdict, the date and up to six earlier scores for the same site so the panel can say whether it got worse — it is stored in this browser and is never uploaded, because the same list held on our side would be a record of which sites you looked at, which is the thing our anonymous audit log is deliberately built not to contain. The stack comparison holds exactly one pinned report. The environment diff holds exactly one captured page — its stack, palette and fonts, plus the hostname and the path, never the query string. The colour picker keeps the last hundred colours you sampled alongside the site each came from, for the same reason and under the same rule. Each is replaced rather than accumulated, cleared from its own panel, and removed with the extension.

The colour picker takes a screenshot

There is no way to read a rendered pixel out of a page. The colour on screen at a point is the result of a background, an image, a gradient, an overlay, opacity and blend modes, and no single element reports it — so the loupe magnifies an image of the tab instead. That image is captured by the browser, handed straight to the page script that draws the lens, and discarded when the lens closes. It is never written to disk, never uploaded, and never taken of a tab you have not opened LoupeKit on: the capture runs on the same one-tab grant every other feature does. LoupeKit hides its own panel and overlays for the instant the picture is taken, so what you sample is the site rather than our own interface.

The form checker types into fields

It is the one tool that writes to the page rather than reading it, and it does so to ask the browser a question only a filled field can answer: whether a pattern, a minimum or a maximum rejects a value a person would reasonably type. Each field is written, checked and restored to its original value within the same synchronous pass, so nothing else on the page ever observes the value we wrote. Nothing is submitted. Password and payment-card fields are skipped entirely. Only the browser’s own validation message is returned to the panel — never anything that was in the field.

The Developer tab changes the page

That is what it is for, and every one of its switches is reversible: turning one off restores exactly what it changed — stylesheets re-enabled, inserted labels deleted, marker attributes removed, and any style attribute it stripped put back with the value it held. Reset page does all of them at once. The handful of one-shot actions that cannot be undone — removing form validation, releasing image sizes, re-requesting images past the cache — each say so on their own description, and a reload is what puts them back. Nothing the tab reads or writes is transmitted, and none of it survives the tab being closed.

Its cookie view reads document.cookie — the same cookies the page itself can read — rather than asking for the browser’s cookies permission, which would grant access to every cookie on every site the extension can reach. HttpOnly cookies are therefore invisible to it, which is exactly what that flag is for. Nothing read there is stored, logged or sent anywhere.

The User-Agent switcher

When you switch it on, the browser asks for permission to rewrite request headers, and the extension installs one rule: it sets the User-Agent header and the two client hints that travel with it. It applies to every tab, not only the one you invoked LoupeKit on — there is no per-tab form of that rule — which is why there is a green dot on the toolbar icon and a strip inside the panel for as long as it is on. Nothing is read from any request; the rule only sets an outgoing header, and no record of it leaves your browser. Turning it off removes the rule and hands the permission back in the same action.

The WebRTC address control

A page can open a peer connection with no permission and no click from you, and the candidates it gathers carry addresses of your machine — the public one, and the private one it holds on your own network. This control changes what the browser is willing to offer. When you choose a policy, the browser asks for the privacy permission, which is broad in name: it covers read and write over every privacy setting the browser exposes, and Chrome offers no narrower form. LoupeKit writes exactly one,network.webRTCIPHandlingPolicy, and reads that same setting to detect when another extension is holding it. It reads and writes no other privacy setting.

Like the switcher above, it applies to every tab — it is a browser setting rather than a page one — so the same green dot appears on the toolbar icon while it is on. Choosing Browser default clears the setting and removes the permission in the same action, in that order. Nothing about the choice, the setting or any address is transmitted anywhere: the whole feature is a call to the browser’s own settings API.

If you uninstall

The browser opens a feedback page on this site afterwards. It carries the version you had installed and nothing else — no device identifier, no plan, no install date, nothing that could connect a removal to any audit you ever ran. Everything the extension kept was kept in your browser, so it goes with it: the audit history, the picked colours, the pinned stack and any API key you had saved. There is nothing on our side to delete.

Requests made on your behalf

Four, and none of them goes to us. They are listed here because "everything stays local" would otherwise be a sentence with an exception hidden behind it.

Domain and DNS lookups

The Domain tab can look up who a domain is registered to and what its DNS records are. Both lookups are buttons and neither runs on its own, because each one tells somebody else which site you are looking at — which is the thing the rest of this page says LoupeKit does not do.

The rest of that tab — the page's language, its favicons and the measurement ids it carries — is read from the document you already have open and is never transmitted at all.

Reading the page's own response headers

Stack detection makes a single HEAD request to the page you already have open, when you open the panel. It is how a CDN, a cache or a web server can be identified at all — those leave nothing in the page itself, only a header on its response.

Fetching the page again, for the rendering check

SEO → Rendering compares the HTML a server sends with the page your browser ended up with — the difference a search crawler sees. Answering that needs the served document, so this is the one reader that asks for a page rather than reading the one in front of it. It is a button and it never runs on its own.

Asking the site for robots.txt and its sitemaps

SEO → Crawler answers whether a search engine or an AI crawler may fetch the page you are on. That is decided in a file the page never loads, so this reader asks the site for it. Two buttons, two different amounts of asking, and neither happens until you press one — the panel names the exact files above them.

Checking whether a build ships its source maps

Tools → Source maps answers a question nothing in a browser shows: whether the scripts on this page serve a map, and what that map exposes. It is a button and it never runs on its own.

Looking up field data (Pro)

Tools → Load timing can look up what real visitors to the page got over the last 28 days. That data lives in Google’s Chrome UX Report and is keyed on the address, sothis is the one feature where the address of a page you are looking at leaves your machine. It is a button, it never runs on its own, and the panel says so above it.

Showing you an image, or copying one

In the asset list, hovering a row previews that image, and pressing PNG copies it to your clipboard. For an inline SVG nothing is fetched at all — the markup is already in the panel. For a linked image both point at the same URL the page itself loaded, so it is normally served from your browser's cache and no new request leaves at all. When one does, it is sent with no referrer and no credentials, only at the moment you hover or press the button, and never to us.

Copying a linked image needs the server to permit a cross-origin read. Many do not, and LoupeKit holds no permission for other sites that could work around it — in that case the button copies the image's URL instead and says so, rather than reporting a success it did not have.

The Server tab (Pro)

Six readings ask our server to fetch the page you are on, from its own address, signed out: what each crawler is served, what the very first response already sets before any consent banner, what the site does when asked for in another language, how each asset is cached and compressed, whether the hosts it links to still answer, and what link preview each platform would draw. None of these can be answered from your browser — a browser cannot become Googlebot, cannot un-see a banner that has already run, and cannot check a cross-origin link without announcing you to it.

They are grouped into one tab with one paragraph above them, because a promise that holds on one screen and not the next is not a promise. Nothing here runs on its own; every button says what it will request before you press it.

Reports you publish at a link

The one feature that keeps anything about a page on our server, and it only runs when you press Publish. What is being sold is the hosting: an audit dies when the panel closes, and an extension cannot produce a link.

Opening on page load

By default LoupeKit can only reach the tab you clicked the toolbar button on, and that access ends at the next navigation. There is one setting that changes this: Open on page load, which is off unless you turn it on. Switching it on asks your browser for access to every site — appearing before you click cannot be done any other way — and switching it off hands that access straight back.

What it does not change is what leaves your browser. An audit still runs only when you press the button, still sends only the fields listed above, and still sends nothing at all from a page you did not audit. The setting decides when the panel appears, not what is collected.

Your own AI keys

If you add an OpenAI, Anthropic or Google key, it is stored in your browser's extension storage and sent directly from your browser to that provider. It never reaches our servers — not in a request, not in a log line, not in an error report. We never see it, so we cannot lose it.

Extension storage is not encrypted. Anything running in this extension can read a saved key, and so can anything with access to your browser profile on disk. That is a real limitation and the settings screen states it before you paste anything. Remove a key there and the browser permission for that provider is handed back with it.

Payments

Checkout is handled by Stripe on Stripe's own pages. Card details never touch our infrastructure. We receive a customer id, a plan, and the email you gave Stripe.

This website

No analytics, no cookies, no tag manager, no embedded third-party content. Fonts are served from this domain. There would be something absurd about a privacy page that loaded a tracker to tell you it was measuring how many people read it.

Accounts and team sharing

Only if you sign in. Nothing below happens otherwise, and every other feature keeps working without it.

What a shared item contains

A shared palette or environment snapshot is not the extraction the panel shows you. It is rebuilt from a small set of named fields before it is sent, and rebuilt again from those same fields on our side — so a client that sent more would not succeed in storing it.

Removing your data

Uninstalling the extension deletes the device id, meaning nothing links you to the stored row any more. To have the row itself deleted, email[email protected] with your device id — it is shown in the extension's settings.