# TraceTail: the complete documentation > TraceTail is a browser fingerprinting API for recognizing returning visitors without cookies. Its JavaScript SDK (a script tag or the npm package @tracetail/js) turns 40+ browser signals into a stable visitor ID with 99.6% accuracy, the same in normal and incognito windows. It works without an API key for a quick start; with a key, every identification is registered with the API, checked for automation and shown in a dashboard. 1,000 identification requests are free every month, then $0.10 per 1,000. Everything needed to choose and integrate TraceTail 3.1.2, in one Markdown file: the documentation, pricing and comparison pages. Each page is also available on its own by adding .md to its URL (https://tracetail.io/docs.md), and https://tracetail.io/llms.txt is the short map. ## Contents - [TraceTail — Browser fingerprinting API with 99.6% accuracy](https://tracetail.io/index.md) - [Documentation — TraceTail](https://tracetail.io/docs.md) - [Pricing — TraceTail](https://tracetail.io/pricing.md) - [TraceTail vs FingerprintJS — pricing and features compared](https://tracetail.io/vs-fingerprintjs.md) - [FingerprintJS alternatives compared: TraceTail, Fingerprint, ThumbmarkJS and ClientJS](https://tracetail.io/fingerprintjs-alternatives.md) --- 99.6% accuracy # Browser fingerprinting that just works Recognize returning visitors without cookies or local storage. TraceTail turns 40+ browser signals into a stable visitor ID that stays the same in normal and incognito windows. [Get started free](https://tracetail.io/auth) [See the live demo](https://tracetail.io/live-demo) - 1,000 requests free every month - No credit card required - SDK under 10 KB gzipped ## Your browser's visitor ID This is the ID TraceTail computes for the browser you're using right now, and every signal behind it. ## Live in three steps 1. ### Create a free account Sign in with your email address. No card needed. 2. ### Register your domain Get an API key that only works on the domains you list. 3. ### Add the snippet Send the visitorId to your backend with the request you want to recognize. [Read the quick start](https://tracetail.io/docs#quickstart) **Script tag** **Before \** ```html ``` **npm** **npm install @tracetail/js** ```javascript import { TraceTail } from '@tracetail/js'; const tracetail = new TraceTail({ apiKey: 'YOUR_API_KEY', endpoint: 'https://tracetail.io/api' }); const { visitorId } = await tracetail.generateFingerprint(); ``` ## Everything you need to recognize a device One visitor ID per browser, computed in milliseconds, with the controls a production site needs. ### No cookies, no storage The ID is computed from the browser itself, so clearing cookies changes nothing, and normal and incognito windows get the same ID. ### 40+ browser signals Canvas and WebGL rendering, 24 font checks, screen, time zone, language and hardware traits, hashed into one visitor ID. ### Flags automated browsers With an API key, each identification includes an isBot flag for automated browsers such as headless Chrome and WebDriver. ### Domain-locked API keys Keys only work on the domains you register for them, so they are safe to ship in your page HTML. ### Under 10 KB gzipped One small script tag or npm package. Works with React, Vue, Angular or plain JavaScript. ### On Cloudflare's network The API runs on Cloudflare Workers, close to your visitors. Your dashboard shows requests and visitors for every key. Use cases ## What teams use a visitor ID for ### Account security Spot a sign-in from a device the account has never used and ask for a second factor only then. ### Signup and promo abuse See when one device opens account after account to collect the same welcome offer. ### Bots and scrapers Rate-limit by visitor ID instead of IP address, and treat automated browsers differently. ### Checkout fraud Notice one device trying card after card, even when it rotates email addresses and IPs. ## Pay only for what you use 1,000 requests free every month, then $0.10 per 1,000. The same rate at every volume. - 10,000 requests a month $0.90 - 100,000 requests a month $9.90 - 1,000,000 requests a month $99.90 [See pricing details](https://tracetail.io/pricing) ## Questions - What is TraceTail? TraceTail is a browser fingerprinting API. Its JavaScript SDK turns 40+ browser signals into a stable visitor ID, so your site recognizes a returning browser without cookies, for example to stop free-trial abuse, link duplicate accounts or flag a sign-in from a new device. - How accurate is TraceTail? TraceTail identifies returning browsers with 99.6% accuracy, and the visitor ID stays the same in normal and incognito windows. - Does TraceTail use cookies? No. The visitor ID is computed from the browser's own traits every time, so nothing is stored in the browser and clearing cookies or site data does not change it. - Is TraceTail free? Every account gets 1,000 identification requests free each month, with no credit card. After that it costs $0.10 per 1,000 requests at every volume. Running the SDK without an API key is free. - Can I try it without signing up? Yes. Add \ to any page and call TraceTail.generateFingerprint(): without an API key the SDK computes the visitor ID in the browser and sends nothing. - Which frameworks does TraceTail work with? Any site that runs in a browser: a script tag for plain HTML, and the @tracetail/js npm package for React, Next.js, Vue, Nuxt, Angular, Svelte and other bundled apps. - Do ad blockers stop TraceTail? Blockers that stop fingerprinting services by their domain can block tracetail.io. Serve the SDK and the API from a path on your own site instead (one click if the site is on Cloudflare, or a small proxy on any other host) and the browser only ever talks to your domain. - Can my server check a visitor ID? Yes. The browser can send any value, so TraceTail's Server API lets your backend look up the identification behind a visitor ID with a secret server key before trusting it for a sign-up, login or payment. - How is TraceTail different from FingerprintJS? FingerprintJS is an open-source library that computes a hash in the browser and has no server behind it. TraceTail also registers each identification, checks it for automation and lets your server verify it. Fingerprint, the company behind FingerprintJS, sells a hosted service that costs considerably more at the same volume. - Can an AI coding agent add TraceTail? Yes. Point Claude Code, Codex, Cursor or another agent at https\://tracetail.io/llms.txt, connect the TraceTail MCP server at https\://tracetail.io/mcp, or install the TraceTail skill with npx skills add https\://tracetail.io. ## Start identifying visitors today 1,000 requests free every month, no credit card required. Add a card only when you need more. [Get started free](https://tracetail.io/auth) [Read the docs](https://tracetail.io/docs) --- Documentation # Recognize returning visitors TraceTail gives every browser a stable visitor ID with 99.6% accuracy, computed from 40+ browser signals without cookies or local storage. Add one script tag, send the ID to your backend, and recognize the visitor next time. ## Quick start 1. ### Try it without a key Paste this before `` on any page, including localhost. Without an API key the SDK computes the visitor ID in the browser and sends nothing, so nothing is recorded in a dashboard. **index.html** ```html ``` 2. ### Add your API key Create an account and a key for your site's domain. Every account includes 1,000 identification requests a month, with no card. With `data-api-key` on the tag, each identification is also registered with the API: it counts toward your usage, shows in your dashboard and comes back with a `requestId` and a bot check. **index.html** ```html ``` [Get an API key](https://tracetail.io/auth) 3. ### Use the visitor ID Send `visitorId` to your backend with the request you want to recognize, such as a sign-up, login or checkout, and store it with that event. The same browser gets the same ID on later visits, in normal and incognito windows, and adding a key never changes it. To check on your server that an ID really came from TraceTail, send the `requestId` too and verify it with the [Server API](https://tracetail.io/docs#server-api). Bundling your JavaScript? [Install](https://tracetail.io/docs#install) covers npm, React, Vue and Angular. ## Keys and domains An API key is `tt_` followed by 64 hexadecimal characters. Keys are public by design (they ship in your page's HTML), and the domain lock is what keeps them safe: the API accepts a key only from pages on the domain it was created for. | Key created for | Accepted page origins | | --------------- | ------------------------------------------------------------------------- | | `example.com` | example.com, www\.example.com and any subdomain, such as shop.example.com | | `localhost` | localhost, 127.0.0.1 and \[::1], on any port | Browsers send the page's origin with every request (the `Origin` header, or `Referer` as a fallback), and the API compares it with the key's domain. Requests from any other site, and requests without either header (from a server or curl), are rejected with `401 DOMAIN_MISMATCH`. Create one key per site in [Settings](https://tracetail.io/settings). For local development, add a key for `localhost`. Your backend uses a different, secret key with the [Server API](https://tracetail.io/docs#server-api). ### Usage and limits - 1,000 identification requests a month are free, with no card. An account without usage billing stops there: further requests get `429 QUOTA_EXCEEDED` until the 1st of the next month (UTC). - Add a card in Settings → Billing to lift the cap. Requests beyond the free 1,000 cost $0.10 per 1,000, the same rate at every volume. See [Pricing](https://tracetail.io/pricing). - Only successful identification requests (`/api/id` and `/api/id/detail`) count. Keyless mode and failed requests are free. - Rate limits: 100 requests per second per key, and 600 requests a minute from one IP address. Beyond that the API answers `429 RATE_LIMITED` with a `Retry-After` header. ## Install Use the script tag on any site, or the npm package if you bundle your JavaScript. Both compute the same visitor ID. **Script tag** Place the tag before ``. It loads synchronously, so `TraceTail` is ready on the next line. **index.html** ```html ``` To pin a release, load `https://tracetail.io/sdk/v3.1.2/sdk.js` instead. That file never changes, so you can add an `integrity` attribute. Compute the hash once per release and prefix it with `sha384-`: **Terminal** ```bash curl -s https://tracetail.io/sdk/v3.1.2/sdk.js | openssl dgst -sha384 -binary | openssl base64 -A ``` **index.html** ```html ``` **npm** **Terminal** ```bash npm install @tracetail/js ``` **tracetail.ts** ```typescript import { TraceTail } from '@tracetail/js'; const tracetail = new TraceTail({ apiKey: 'YOUR_API_KEY', endpoint: 'https://tracetail.io/api' }); const { visitorId } = await tracetail.generateFingerprint(); ``` Create one client and reuse it. It keeps the result in memory, so later calls on the same page return at once, and calls made at the same time share one request. **React** **Terminal** ```bash npm install @tracetail/js ``` **useVisitorId.ts** ```typescript import { useEffect, useState } from 'react'; import { TraceTail } from '@tracetail/js'; const tracetail = new TraceTail({ apiKey: 'YOUR_API_KEY', endpoint: 'https://tracetail.io/api' }); export function useVisitorId() { const [visitorId, setVisitorId] = useState(); useEffect(() => { tracetail.generateFingerprint().then((result) => setVisitorId(result.visitorId)); }, []); return visitorId; } ``` Call `useVisitorId()` in any component. `useEffect` only runs in the browser, so the hook is safe with server-side rendering. **Vue** **Terminal** ```bash npm install @tracetail/js ``` **useVisitorId.ts** ```typescript import { onMounted, ref } from 'vue'; import { TraceTail } from '@tracetail/js'; const tracetail = new TraceTail({ apiKey: 'YOUR_API_KEY', endpoint: 'https://tracetail.io/api' }); export function useVisitorId() { const visitorId = ref(); onMounted(async () => { const result = await tracetail.generateFingerprint(); visitorId.value = result.visitorId; }); return visitorId; } ``` Call `useVisitorId()` in `

Visitor: {visitorId}

``` onMount only runs in the browser, so the component is safe with SvelteKit's server rendering. Send visitorId to your backend with the request you want to recognize (sign-up, login, checkout) and store it with that event. ## AI coding agents TraceTail is built for AI coding agents to add on their own. Keyless mode works without an account, and everything an agent needs is machine-readable: an MCP server, an agent skill, the docs as Markdown and an OpenAPI description. Paste this into Claude Code, Codex, Cursor or any other agent: **Prompt** ```text Add TraceTail browser fingerprinting to this app. Read https://tracetail.io/llms.txt (or use the TraceTail MCP server), add the SDK the way this app's framework needs it, get its API keys with a setup link, send the visitor ID to the backend with sign-ups and logins, and verify it there with the Server API. ``` ### MCP server `https://tracetail.io/mcp` is a remote MCP server (Streamable HTTP) that needs no account or key. It works with the 2025 and 2026 versions of the protocol. Add it to your agent: **Claude Code** **Terminal** ```bash claude mcp add --transport http tracetail https://tracetail.io/mcp ``` **Codex** **Terminal** ```bash codex mcp add tracetail --url https://tracetail.io/mcp ``` **Cursor** **.cursor/mcp.json** ```json { "mcpServers": { "tracetail": { "url": "https://tracetail.io/mcp" } } } ``` **VS Code** **.vscode/mcp.json** ```json { "servers": { "tracetail": { "type": "http", "url": "https://tracetail.io/mcp" } } } ``` **Gemini CLI** **\~/.gemini/settings.json** ```json { "mcpServers": { "tracetail": { "httpUrl": "https://tracetail.io/mcp" } } } ``` **Windsurf** **mcp\_config.json** ```json { "mcpServers": { "tracetail": { "serverUrl": "https://tracetail.io/mcp" } } } ``` - `get_integration_code` tool The exact install command and code to add TraceTail browser fingerprinting (a stable visitor ID for each browser, without cookies) to a web app built with the given framework, with where each file goes. Works without an API key (keyless mode, no sign-up); pass the site's public API key to fill it in. - `search_docs` tool Searches TraceTail's documentation, pricing, comparison and blog for a topic (e.g. "next.js", "rate limits", "ad blockers", "GDPR", "server-side verification") and returns the matching sections with their URLs. - `read_docs_page` tool Returns a page of the TraceTail site as Markdown: "/docs" (the full documentation), "/pricing", "/vs-fingerprintjs", "/" (overview) or a blog post. - `get_pricing` tool TraceTail's prices and limits (1,000 identification requests free every month, then $0.10 per 1,000), with the monthly cost at a given volume and the same volume on Fingerprint (FingerprintJS) for comparison. - `create_setup_link` tool Gets TraceTail API keys for the person you work for: returns a link to give them, where they sign in with an emailed code and add a card or continue without a card, and a token. Then call check\_setup\_link with its id and token every few seconds: once they've finished, it returns an API key for each site (and a server key if you asked), once. - `check_setup_link` tool Where a setup link stands. The first check after the person finishes returns the API keys (and the server key, if requested); they are not shown again. While they're still on the page, check again every few seconds. It also offers every page of the docs as a resource and an `add-tracetail` prompt. Its card is at `/.well-known/mcp/server-card.json`. ### API keys through a setup link An agent can get the keys itself. It creates a setup link and gives it to you; you sign in with an emailed code, then add a card (nothing is charged until you go past the free 1,000 requests a month) or continue without a card, and the agent's next check returns an API key for each site, and a server key if it asked for one. Over MCP these are the `create_setup_link` and `check_setup_link` tools; over HTTP: **Terminal** ```bash curl -X POST https://tracetail.io/api/v1/setup-links \ -H "Content-Type: application/json" \ -d '{"domains": ["example.com", "localhost"], "serverKey": true}' ``` **201 Created** ```json { "id": "sl_5f0c9a…", "status": "pending", "setupUrl": "https://tracetail.io/setup/sl_5f0c9a…", "token": "tt_setup_8e41d2…", "next": "Waiting for the person to open the setup link and finish…" } ``` **Terminal** ```bash curl https://tracetail.io/api/v1/setup-links/sl_5f0c9a… \ -H "Authorization: Bearer tt_setup_8e41d2…" ``` **200 OK, once you have finished** ```json { "status": "delivered", "account": { "email": "y***@example.com", "paidUsage": true }, "apiKeys": [ { "domain": "example.com", "apiKey": "tt_…" }, { "domain": "localhost", "apiKey": "tt_…" } ], "serverKey": "tts_…", "next": "Put each API key in the TraceTail snippet for its site…" } ``` The keys come back once, on the first check after you finish; an account that already has a key for a site keeps it, and gets `null` for that site. A server key reads every visitor your account identifies, so a link only creates one for an account it creates: if you sign in to an account you already have, create the server key yourself in [Settings → API keys](https://tracetail.io/settings). Links work for 24 hours, and the token is the agent's secret: the API refuses requests from browsers. URLs such as `https://www.example.com` or `localhost:3000` are reduced to their domain. ### Agent skill The TraceTail skill teaches an agent the whole integration: the code for each framework, API keys, server-side verification and the limits. Install it with the skills CLI, which reads it from `/.well-known/agent-skills/`: **Terminal** ```bash npx skills add https://tracetail.io ``` ### Docs for LLMs - [/llms.txt](https://tracetail.io/llms.txt): what TraceTail is, a quick start and links to everything. [/llms-full.txt](https://tracetail.io/llms-full.txt) holds the documentation in one file. - Every page has a Markdown copy: add `.md` to its URL (`/docs.md`), or request the page with `Accept: text/markdown`. - [/openapi.json](https://tracetail.io/openapi.json) describes the API (OpenAPI 3.1), and `/.well-known/api-catalog` lists it (RFC 9727). ## First-party setup Serve the SDK and the API from a path on your own site, such as `example.com/k3v9/sdk.js`. The browser then only talks to your domain: blockers and filter lists that stop fingerprinting services by their domain never see TraceTail, and your Content Security Policy needs nothing beyond `'self'`. A small proxy at that path forwards the SDK and identification requests to TraceTail. It sends each visitor's IP address along with a proxy secret, so rate limits and your analytics still see visitors rather than your proxy. Visitor IDs, usage and billing are exactly the same as without it. ### One click on Cloudflare If your site is on Cloudflare, open [Settings → API keys](https://tracetail.io/settings) and choose **First-party** next to the site's key: 1. Create a Cloudflare API token with the link there. It opens Cloudflare with the three permissions the setup needs: Zone · Zone · Read, Zone · Workers Routes · Edit, Account · Workers Scripts · Edit. 2. Paste the token and deploy. TraceTail uploads a Worker named `tracetail-proxy-example-com` with its own proxy secret, routes `example.com/k3v9/*` and `*.example.com/k3v9/*` to it, and checks that it answers. The token is used for that one request and never stored, so you can delete it afterwards. Setting up again gives the Worker a new secret, and a new path moves its routes. ### Any other host Choose **Other hosting** in the same dialog to get a proxy secret, shown once. Keep it on the server as `TRACETAIL_PROXY_SECRET`, never in page HTML. Then run the proxy at your path. For a Worker you deploy yourself, add the variable `TRACETAIL_PATH` and the secret, and route `example.com/k3v9/*` to it. On Next.js 15 or later, add the route handler: **Cloudflare Worker** **worker.js** ```javascript // TraceTail first-party proxy for Cloudflare Workers. // Serves the TraceTail browser SDK and API from a path on your own site, so the browser only // talks to your domain. Docs: https://tracetail.io/docs#first-party // // Variables: TRACETAIL_PATH, e.g. /k3v9 (route the Worker at yoursite.com/k3v9/*), and // TRACETAIL_PROXY_SECRET, your proxy secret from TraceTail (add it as a secret). const UPSTREAM = 'https://tracetail.io'; const FORWARDED_REQUEST_HEADERS = ['accept', 'accept-language', 'content-type', 'origin', 'referer', 'user-agent', 'x-api-key', 'x-tracetail-sdk']; const FORWARDED_RESPONSE_HEADERS = ['content-type', 'cache-control', 'etag', 'last-modified', 'expires', 'vary', 'x-content-type-options', 'cross-origin-resource-policy', 'retry-after', 'warning', 'x-tracetail-proxy', 'access-control-allow-origin', 'access-control-allow-headers', 'access-control-allow-methods', 'access-control-expose-headers', 'access-control-max-age']; const FORWARDED_SDK_HEADERS = ['if-none-match', 'if-modified-since']; const API_PATHS = ['/api/id', '/api/id/detail']; const MAX_BODY_BYTES = 49152; export default { async fetch(request, env) { const base = '/' + String(env.TRACETAIL_PATH || '').replace(/^\/+|\/+$/g, ''); const url = new URL(request.url); if (base === '/' || !url.pathname.startsWith(base + '/')) return notFound(); const path = url.pathname.slice(base.length); const upstream = String(env.TRACETAIL_UPSTREAM || UPSTREAM).replace(/\/+$/, ''); if (/^\/(?:sdk\/v\d+\.\d+\.\d+\/)?sdk\.js$/.test(path)) { if (request.method !== 'GET' && request.method !== 'HEAD') return new Response(null, { status: 405, headers: { Allow: 'GET, HEAD' } }); // Only what lets the browser revalidate the copy it holds (TraceTail answers 304). return passThrough(await fetch(upstream + path, { method: request.method, headers: copied(request, FORWARDED_SDK_HEADERS) })); } if (API_PATHS.includes(path) || path === '/health') { const target = path === '/health' ? '/api/proxy/status' : path; const forwarded = await forwardedRequest(request, env); if (!forwarded) return Response.json({"error":"Request body is too large.","code":"PAYLOAD_TOO_LARGE"}, { status: 413 }); return passThrough(await fetch(upstream + target, forwarded)); } return notFound(); }, }; // Only the headers TraceTail needs: never your site's cookies or other credentials. // Null when the body is larger than TraceTail accepts. async function forwardedRequest(request, env) { const headers = copied(request, FORWARDED_REQUEST_HEADERS); const visitorIp = request.headers.get('cf-connecting-ip'); if (visitorIp) headers.set('x-tracetail-client-ip', visitorIp); if (env.TRACETAIL_PROXY_SECRET) headers.set('x-tracetail-proxy-secret', env.TRACETAIL_PROXY_SECRET); const hasBody = request.method !== 'GET' && request.method !== 'HEAD'; const body = hasBody ? await readBody(request) : undefined; return body === null ? null : { method: request.method, headers, body }; } function copied(request, names) { const headers = new Headers(); for (const name of names) { const value = request.headers.get(name); if (value) headers.set(name, value); } return headers; } // TraceTail refuses bodies over MAX_BODY_BYTES: stop reading there rather than hold any size in memory. async function readBody(request) { if (Number(request.headers.get('content-length') || 0) > MAX_BODY_BYTES) return null; if (!request.body) return undefined; const reader = request.body.getReader(); const chunks = []; let size = 0; for (;;) { const { done, value } = await reader.read(); if (done) break; size += value.byteLength; if (size > MAX_BODY_BYTES) { await reader.cancel(); return null; } chunks.push(value); } const body = new Uint8Array(size); let offset = 0; for (const chunk of chunks) { body.set(chunk, offset); offset += chunk.byteLength; } return body.buffer; } // Only the headers about this response: TraceTail's site-wide ones (HSTS, CSP) would apply to your site. function passThrough(response) { const headers = new Headers(); for (const name of FORWARDED_RESPONSE_HEADERS) { const value = response.headers.get(name); if (value) headers.set(name, value); } return new Response(response.body, { status: response.status, statusText: response.statusText, headers }); } function notFound() { return new Response('Not found', { status: 404 }); } ``` **Next.js** **app/k3v9/\[...route]/route.ts** ```typescript // app/k3v9/[...route]/route.ts — TraceTail first-party proxy for Next.js 15+ (App Router). // Serves the TraceTail SDK and API from /k3v9 on your own site. Docs: https://tracetail.io/docs#first-party // Set TRACETAIL_PROXY_SECRET (your proxy secret from TraceTail) in your environment. const UPSTREAM = 'https://tracetail.io'; const FORWARDED_REQUEST_HEADERS = ['accept', 'accept-language', 'content-type', 'origin', 'referer', 'user-agent', 'x-api-key', 'x-tracetail-sdk']; const FORWARDED_RESPONSE_HEADERS = ['content-type', 'cache-control', 'etag', 'last-modified', 'expires', 'vary', 'x-content-type-options', 'cross-origin-resource-policy', 'retry-after', 'warning', 'x-tracetail-proxy', 'access-control-allow-origin', 'access-control-allow-headers', 'access-control-allow-methods', 'access-control-expose-headers', 'access-control-max-age']; const FORWARDED_SDK_HEADERS = ['if-none-match', 'if-modified-since']; const API_PATHS = ['/api/id', '/api/id/detail']; const MAX_BODY_BYTES = 49152; async function proxy(request: Request, context: { params: Promise<{ route: string[] }> }): Promise { const path = '/' + (await context.params).route.join('/'); const isSdk = /^\/(?:sdk\/v\d+\.\d+\.\d+\/)?sdk\.js$/.test(path); let target: string; if (isSdk) target = path; else if (API_PATHS.includes(path)) target = path; else if (path === '/health') target = '/api/proxy/status'; else return new Response('Not found', { status: 404 }); // Only the headers TraceTail needs: never your site's cookies or other credentials. For the SDK, // only what lets the browser revalidate the copy it holds (TraceTail answers 304). const headers = new Headers(); for (const name of isSdk ? FORWARDED_SDK_HEADERS : FORWARDED_REQUEST_HEADERS) { const value = request.headers.get(name); if (value) headers.set(name, value); } if (isSdk) return passThrough(await fetch(UPSTREAM + target, { method: request.method, headers, cache: 'no-store' })); // X-Real-IP as your host sets it (Vercel does; behind nginx, set it to $remote_addr). Otherwise the // address your nearest proxy appended to X-Forwarded-For: the entries before it come from the visitor. const visitorIp = request.headers.get('x-real-ip') ?? request.headers.get('x-forwarded-for')?.split(',').pop()?.trim(); if (visitorIp) headers.set('x-tracetail-client-ip', visitorIp); if (process.env.TRACETAIL_PROXY_SECRET) headers.set('x-tracetail-proxy-secret', process.env.TRACETAIL_PROXY_SECRET); const hasBody = request.method !== 'GET' && request.method !== 'HEAD'; const body = hasBody ? await readBody(request) : undefined; if (body === null) return Response.json({"error":"Request body is too large.","code":"PAYLOAD_TOO_LARGE"}, { status: 413 }); return passThrough(await fetch(UPSTREAM + target, { method: request.method, headers, body, cache: 'no-store' })); } // Only the headers about this response: TraceTail's site-wide ones (HSTS, CSP) would apply to your site. function passThrough(upstream: Response): Response { const responseHeaders = new Headers(); for (const name of FORWARDED_RESPONSE_HEADERS) { const value = upstream.headers.get(name); if (value) responseHeaders.set(name, value); } return new Response(upstream.body, { status: upstream.status, statusText: upstream.statusText, headers: responseHeaders }); } // TraceTail refuses bodies over MAX_BODY_BYTES: stop reading there rather than hold any size in memory. async function readBody(request: Request): Promise { if (Number(request.headers.get('content-length') || 0) > MAX_BODY_BYTES) return null; if (!request.body) return undefined; const reader = request.body.getReader(); const chunks: Uint8Array[] = []; let size = 0; for (;;) { const { done, value } = await reader.read(); if (done) break; size += value.byteLength; if (size > MAX_BODY_BYTES) { await reader.cancel(); return null; } chunks.push(value); } const body = new Uint8Array(size); let offset = 0; for (const chunk of chunks) { body.set(chunk, offset); offset += chunk.byteLength; } return body.buffer; } export const GET = proxy; export const HEAD = proxy; export const POST = proxy; ``` Anything else that forwards HTTP requests works too, such as nginx, Express or a serverless function. Forward these: | On your site | Forward to | | --------------------------------------------- | ---------------------------------------------------------------------------------- | | `GET /k3v9/sdk.js` | `https://tracetail.io/sdk.js`, and `/k3v9/sdk/v3.1.2/sdk.js` to the pinned release | | `POST /k3v9/api/id` and `/k3v9/api/id/detail` | `https://tracetail.io/api/id` and `/api/id/detail`, with the body unchanged | | `GET /k3v9/health` | `https://tracetail.io/api/proxy/status` | Forward only these request headers: `Accept`, `Accept-Language`, `Content-Type`, `Origin`, `Referer`, `User-Agent`, `X-API-Key`, `X-TraceTail-SDK`, never cookies or `Authorization`. Add `X-TraceTail-Client-IP` with the visitor's IP address, as your own server sees it rather than from a header the visitor could set, and `X-TraceTail-Proxy-Secret` with your proxy secret. Pass back only these response headers: `Content-Type`, `Cache-Control`, `ETag`, `Last-Modified`, `Expires`, `Vary`, `X-Content-Type-Options`, `Cross-Origin-Resource-Policy`, `Retry-After`, `Warning`, `X-TraceTail-Proxy`, `Access-Control-Allow-Origin`, `Access-Control-Allow-Headers`, `Access-Control-Allow-Methods`, `Access-Control-Expose-Headers`, `Access-Control-Max-Age`. TraceTail's others are set for tracetail.io as a whole: on your domain, its `Strict-Transport-Security` would force HTTPS on every subdomain for a year, and its `Content-Security-Policy` isn't yours. ### Load the SDK through the proxy Point the tag at your path. `data-endpoint` sends the identification requests through the proxy too: **index.html** ```html ``` With npm, set `endpoint`: **tracetail.ts** ```typescript import { TraceTail } from '@tracetail/js'; const tracetail = new TraceTail({ apiKey: 'YOUR_API_KEY', endpoint: '/k3v9/api' }); const { visitorId } = await tracetail.generateFingerprint(); ``` ### Check it Open the health check on your site. `trusted` means TraceTail accepts the proxy's secret, and `visitorIp` should be your own IP address: ```http GET https://example.com/k3v9/health { "proxy": "trusted", "domain": "example.com", "visitorIp": "203.0.113.7" } ``` `untrusted` with the reason `no-secret` means the proxy isn't sending the secret, and `unknown-secret` that the secret isn't current: set the proxy up again. Identification responses through a working proxy carry `X-TraceTail-Proxy: trusted`, and **Check the proxy** in Settings runs the same test from TraceTail. Pick a path that doesn't look like analytics, because filter lists match words such as `track`, `analytics` and `collect` in URLs. The dashboard suggests a random one. ## SDK reference The script tag and the npm package have the same methods and return the same result. ### Script tag: window\.TraceTail The script configures itself from its own tag: `data-api-key` (leave it out for keyless mode) and `data-endpoint` (defaults to `https://tracetail.io/api`). Loading it collects and sends nothing; the SDK starts when you call `generateFingerprint()`. - `generateFingerprint(options?)` Promise\ Identifies this browser. See the options and the result below. - `getFingerprint(options?)` Promise\ Same as `generateFingerprint`. - `configure(options)` void Changes options at runtime, merged into the current ones; `configure({ debug: true })` keeps the key from the tag. It also clears the cached result. - `getComplianceContext()` ComplianceContext Privacy and region hints from the browser; see [Privacy and compliance](https://tracetail.io/docs#privacy). - `version` string The SDK version, currently `3.1.2`. Using TypeScript with the script tag? Type declarations for the global are at `https://tracetail.io/sdk/tracetail.d.ts`. ### npm: new TraceTail(options) Import it with `import { TraceTail } from '@tracetail/js'`. An instance has `generateFingerprint(options?)`, `getComplianceContext()` and `version`. `getComplianceContext` is also a named export, for use without an instance. Importing the package and creating the client never touch the browser, so both are safe during server-side rendering. ### Options For `new TraceTail(options)` and `TraceTail.configure(options)`: - `apiKey` string Your API key. Leave it out for keyless mode. - `endpoint` string The API base URL. Default: `https://tracetail.io/api` - `timeout` number How long to wait for the API, in milliseconds. A timeout sets `error.code` to `TIMEOUT`. Default: `5000` - `debug` boolean Logs each step and any API error to the console. Default: `false` ### generateFingerprint(options?) - `verbose` boolean Adds the collected signals to the result as `components`. - `detailed` boolean Calls `/api/id/detail` instead of `/api/id`; the result adds `riskAssessment` and `botDetection`. Needs a key; keyless mode ignores it. - `refresh` boolean Skips the in-memory cache and identifies again. ### Result - `visitorId` string `fp3_` followed by 16 hex characters. The same in keyless and keyed mode. - `quality` { score, reasons } Signal completeness from 0 to 1, with reasons for missing or reduced information. This is not match probability. - `confidence` number Deprecated alias of quality.score. Not an accuracy estimate. - `verification` string In keyed mode: consistent when the server recomputed the ID from the signals; unverified for legacy requests. This does not authenticate a device. - `processingTime` number Milliseconds from the call to the result, measured in the browser, including the API call in keyed mode. - `requestId` string? Keyed mode: the ID of the registered request, which your server can verify with the [Server API](https://tracetail.io/docs#server-api). - `isBot` boolean? Keyed mode: true for automated browsers (headless Chrome, WebDriver). - `riskAssessment, botDetection` object? With `detailed: true`. See [REST API](https://tracetail.io/docs#rest-api). - `components` object? With `verbose: true`: the signals behind the ID (`basic`, `hardware`, `canvas`, `webgl`, `fonts`), plus `automation`, which is not part of the ID. - `error` { status, code, message }? Keyed mode, when the API call failed. The visitor ID is still returned. ### Handling errors In keyed mode, `generateFingerprint()` doesn't reject when the API call fails. It resolves with the visitor ID, computed in the browser, and an `error`, so your page keeps working. Failed results aren't cached: the next call tries the API again. ```javascript const result = await TraceTail.generateFingerprint(); if (result.error) { // For example 401 DOMAIN_MISMATCH, 429 RATE_LIMITED, or 0 NETWORK_ERROR console.warn('TraceTail:', result.error.status, result.error.code, result.error.message); } ``` `error.status` is the HTTP status, or `0` when the request never completed. `error.code` is one of the API codes under [Errors](https://tracetail.io/docs#rest-api-errors), or one set by the SDK: `NETWORK_ERROR` and `TIMEOUT` (status 0), `HTTP_ERROR` (an error response without a code) and `INVALID_RESPONSE` (a success response the SDK couldn't read). `generateFingerprint()` only rejects when it can't run at all, for example on a server during rendering. ### Debugging Call `TraceTail.configure({ debug: true })`, or pass `debug: true` to `new TraceTail()`. The SDK then logs each step and any API error to the console. ## REST API The SDK makes these calls for you. They're documented so you can set up your Content Security Policy and read the requests in your browser's network panel. Every call must come from a page on the key's domain. ### Identify: POST /api/id **Request** ```http POST https://tracetail.io/api/id Content-Type: application/json X-API-Key: YOUR_API_KEY X-TraceTail-SDK: js/3.1.2 Origin: https://www.example.com { "visitorId": "fp3_e8fc80d60f8c3571", "components": { "basic": { "browser": "chrome", "os": "macos", "language": "en-US", "platform": "MacIntel", "cookieEnabled": true, "screen": [ 1440, 900 ], "colorDepth": 30, "timezone": "Europe/Berlin" }, "hardware": { "hardwareConcurrency": 8, "maxTouchPoints": 0, "deviceMemory": 8 }, "canvas": { "hash": "example", "samples": [ 12480, 9216, 14111, 7340 ] }, "webgl": { "vendor": "WebKit", "renderer": "WebKit WebGL", "version": "WebGL 1.0", "unmaskedVendor": "Apple Inc.", "unmaskedRenderer": "Apple GPU" }, "fonts": { "detected": [ "Arial", "Helvetica" ], "count": 2, "hash": "example" }, "automation": { "webdriver": false } }, "confidence": 1 } ``` The body carries the visitor ID computed in the browser, the collected signals (at most 32 KB) and a legacy confidence field. The server validates the complete v3 signals, recomputes the ID and signal quality, and records the request before returning success. The browser adds the `Origin` header, which the API checks against the key's domain. A successful response: **200 OK** ```json { "requestId": "3f8c2a1e-6b4d-4c7a-9e21-5d0f7b8a9c13", "visitorId": "fp3_e8fc80d60f8c3571", "confidence": 1, "quality": { "score": 1, "reasons": [] }, "verification": "consistent", "isBot": false } ``` ### Detailed: POST /api/id/detail Same request. The response adds `riskAssessment` and `botDetection`; the SDK calls this endpoint when you pass `detailed: true`. **200 OK** ```json { "requestId": "3f8c2a1e-6b4d-4c7a-9e21-5d0f7b8a9c13", "visitorId": "fp3_e8fc80d60f8c3571", "confidence": 1, "quality": { "score": 1, "reasons": [] }, "verification": "consistent", "isBot": false, "riskAssessment": { "level": "low", "score": 0, "signals": [] }, "botDetection": { "isBot": false, "verdict": "not_detected", "automationTools": [] } } ``` - `riskAssessment` { level, score, signals } `level` is `low`, `medium` or `high`; `score` runs from 0 to 1; `signals` lists what was found as `{ type, detail }`. Types: `headless_browser`, `webdriver`, `user_agent_mismatch`, `platform_mismatch`, `software_renderer`, `virtual_machine`, `insufficient_signals`. - `botDetection` { isBot, automationTools } `isBot` is true only on direct evidence: a headless browser user agent or `navigator.webdriver`. `automationTools` names what was detected. ### Errors Errors come back as JSON with a code. The SDK hands them to you as `result.error`. **401 Unauthorized** ```json { "error": "This API key is for example.com; the request came from localhost.", "code": "DOMAIN_MISMATCH" } ``` | Status | Code | Meaning | | ------ | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | 400 | `VALIDATION_ERROR` | The body is not valid JSON or lacks a valid ID and complete v3 components. | | 400 | `ID_COMPONENT_MISMATCH` | The ID does not match its signals. Generate a fresh SDK result. | | 401 | `INVALID_KEY` | The key does not exist or was deactivated. | | 409 | `IDEMPOTENCY_CONFLICT` | A retry token was reused with different data. Generate a fresh SDK result. | | 401 | `DOMAIN_MISMATCH` | The page is not on the key’s domain, or the request had no Origin or Referer. | | 402 | `PAYMENT_REQUIRED` | Past the free 1,000 this month with an unpaid or canceled subscription. Update it in Settings → Billing. | | 413 | `PAYLOAD_TOO_LARGE` | The signals are larger than 32 KB. | | 429 | `QUOTA_EXCEEDED` | An account without usage billing used its 1,000 requests this month. Add a card in Settings → Billing, or wait for the 1st (UTC). | | 429 | `RATE_LIMITED` | More than 100 requests a second for the key, or 600 a minute from one IP. Retry after the number of seconds in `Retry-After`. | | 503 | `DB_UNAVAILABLE` | A temporary outage. Retry later. | ### Status `GET https://tracetail.io/api/health` reports whether the API is up, and the [status page](https://tracetail.io/status) shows current incidents. ## Server API Have the page send its `requestId` with the visitor ID, then look it up from your backend. The Server API returns what TraceTail recorded for that request, from your account only. Require `verification: consistent`and check its domain and age. This confirms the ID matches the submitted signals; client signals can still be fabricated, so a fingerprint is not authentication or proof of a human. Bind the action to your application's session and store the request ID with a unique constraint in the same transaction as the action. This prevents replay; reading an identification does not consume it. **In the page** ```javascript const { visitorId, requestId } = await TraceTail.generateFingerprint(); await fetch('/signup', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, visitorId, requestId }), }); ``` ### Server keys The Server API takes a server key: `tts_` followed by 64 hexadecimal characters. Create one in [Settings → API keys](https://tracetail.io/settings), under Server API keys. It's shown once, and TraceTail stores only a hash. Unlike your public key, a server key is a secret that reads your whole account, so keep it on your server, for example in a `TRACETAIL_SERVER_KEY` environment variable. An account can have 5 server keys, and a revoked key stops working within a minute. Send the key in the `Authorization` header as `Bearer tts_…`. A key in the URL or in another header is refused, and so is any request with an `Origin` header: browsers send one, and the Server API sends no CORS headers. ### Verify an identification: GET /api/v1/identifications/:requestId **Terminal** ```bash curl https://tracetail.io/api/v1/identifications/3f8c2a1e-6b4d-4c7a-9e21-5d0f7b8a9c13 \ -H "Authorization: Bearer $TRACETAIL_SERVER_KEY" ``` **200 OK** ```json { "requestId": "3f8c2a1e-6b4d-4c7a-9e21-5d0f7b8a9c13", "visitorId": "fp3_e8fc80d60f8c3571", "domain": "www.example.com", "endpoint": "/api/id", "createdAt": "2026-10-05T09:12:44.000Z", "ipAddress": "203.0.113.7", "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36", "browser": "Chrome", "os": "macOS", "confidence": 1, "riskScore": 0, "quality": { "score": 1, "reasons": [] }, "verification": "consistent", "isBot": false } ``` - `requestId` string The ID the identification returned to the browser. - `visitorId` string The visitor ID the browser sent with it. Compare it with the one your page sent you. - `domain` string The host of the page that made the request, such as `www.example.com`. - `endpoint` string `/api/id` or `/api/id/detail`. - `createdAt` string When TraceTail registered the identification, in ISO 8601 (UTC). - `ipAddress` string | null The visitor's IP address; through a [first-party proxy](https://tracetail.io/docs#first-party), the one it forwarded. - `userAgent` string | null The browser's `User-Agent` header. - `browser, os` string | null Such as `Chrome` and `macOS`, from this request's User-Agent header. - `confidence, riskScore` number | null From 0 to 1, captured for this request: signal completeness (not match probability) and the bot risk score (`riskAssessment.score` on [/api/id/detail](https://tracetail.io/docs#rest-api)). Historical events without an assessment return null. Check that the identification is for the visitor ID your page sent, that it's recent and that it came from your site. A `404` means the requestId is not a successful identification in your account. **Node.js** **verify.js** ```javascript const TEN_MINUTES_MS = 10 * 60 * 1000; // requestId and visitorId as your page sent them. async function verifyIdentification(requestId, visitorId) { const response = await fetch( `https://tracetail.io/api/v1/identifications/${encodeURIComponent(requestId)}`, { headers: { Authorization: `Bearer ${process.env.TRACETAIL_SERVER_KEY}` } }, ); if (response.status === 404) return false; // not an identification in your account if (!response.ok) throw new Error(`TraceTail Server API: HTTP ${response.status}`); const identification = await response.json(); return identification.verification === 'consistent' && identification.visitorId === visitorId && Date.now() >= Date.parse(identification.createdAt) && Date.now() - Date.parse(identification.createdAt) < TEN_MINUTES_MS && (identification.domain === 'example.com' || identification.domain.endsWith('.example.com')); } ``` **Python** **verify.py** ```python import os from datetime import datetime, timedelta, timezone from urllib.parse import quote import requests # request_id and visitor_id as your page sent them. def verify_identification(request_id, visitor_id): response = requests.get( f"https://tracetail.io/api/v1/identifications/{quote(request_id, safe='')}", headers={"Authorization": f"Bearer {os.environ['TRACETAIL_SERVER_KEY']}"}, timeout=5, ) if response.status_code == 404: return False # not an identification in your account response.raise_for_status() identification = response.json() created_at = datetime.fromisoformat(identification["createdAt"].replace("Z", "+00:00")) domain = identification["domain"] return ( identification["verification"] == "consistent" and identification["visitorId"] == visitor_id and created_at <= datetime.now(timezone.utc) and datetime.now(timezone.utc) - created_at < timedelta(minutes=10) and (domain == "example.com" or domain.endswith(".example.com")) ) ``` ### Look up a visitor: GET /api/v1/visitors/:visitorId What your account knows about one visitor, for example to see whether the browser behind a sign-up has been here before: **Request** ```http GET https://tracetail.io/api/v1/visitors/fp3_e8fc80d60f8c3571 Authorization: Bearer tts_… ``` **200 OK** ```json { "visitorId": "fp3_e8fc80d60f8c3571", "firstSeenAt": "2026-08-14T17:03:10.000Z", "lastSeenAt": "2026-10-05T09:12:44.000Z", "visits": 12, "browser": "Chrome", "os": "macOS", "confidence": 1, "riskScore": 0, "recentIdentifications": [ { "requestId": "3f8c2a1e-6b4d-4c7a-9e21-5d0f7b8a9c13", "domain": "www.example.com", "endpoint": "/api/id", "createdAt": "2026-10-05T09:12:44.000Z", "ipAddress": "203.0.113.7", "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" } ] } ``` - `visitorId` string The visitor ID you looked up. - `firstSeenAt` string When TraceTail first identified the visitor for your account (ISO 8601). - `lastSeenAt` string Their latest identification (ISO 8601). - `visits` number Successful identifications of the visitor in the last 90 days. - `browser, os` string | null Signal completeness and bot risk from the latest visit. Older visits may use legacy scoring. - `confidence, riskScore` number | null Signal completeness and bot risk from the latest visit. Older visits may use legacy scoring. - `recentIdentifications` object\[] The latest 20, newest first, each with `requestId`, `domain`, `endpoint`, `createdAt`, `ipAddress` and `userAgent`. ### Errors Errors are JSON with a code, like the identification API's. | Status | Code | Meaning | | ------ | -------------------- | -------------------------------------------------------------------------------------------------------------- | | 400 | `VALIDATION_ERROR` | The `requestId` is not a UUID, or the visitor ID is not one the SDK computes. | | 401 | `INVALID_SERVER_KEY` | No server key in the `Authorization` header, a key in the URL or another header, or an unknown or revoked key. | | 403 | `BROWSER_REQUEST` | The request has an `Origin` header, so it came from a browser. Call the Server API from your server. | | 404 | `NOT_FOUND` | No successful identification, or no visitor, with that ID in your account. Refused requests have no requestId. | | 429 | `RATE_LIMITED` | More than 600 requests a minute with the key. Retry after the number of seconds in `Retry-After`. | | 503 | `DB_UNAVAILABLE` | A temporary outage. Retry later. | ### Limits and retention A server key can make 600 requests a minute, and Server API requests don't count toward your usage. Identifications can be looked up for 90 days, as long as request logs are kept, and a visitor until 180 days after their last visit. Erasing a visitor (see [Privacy and compliance](https://tracetail.io/docs#privacy-erasure)) removes both. ## Privacy and compliance ### What TraceTail stores Keyless mode sends nothing. For keyed requests, TraceTail stores the visitor ID with the collected signals, the request's IP address and its user agent. A visitor's record is deleted after 180 days without a visit, and API request logs after 90 days. ### Erasing a visitor When a visitor asks you to delete their data, open [Settings → Privacy](https://tracetail.io/settings?tab=privacy), enter their visitor ID and confirm. TraceTail deletes the visitor's record (signals, IP address and user agent) and every API log carrying that visitor ID, within your account only. The same erasure is available as `DELETE /api/visitors/:visitorId` for a session signed in to your dashboard, for example from the browser console on tracetail.io. It returns `400` for an invalid ID and `401` when you're signed out. ```javascript const response = await fetch('/api/visitors/fp3_e8fc80d60f8c3571', { method: 'DELETE' }); console.log(await response.json()); // { success: true, visitorId: 'fp3_e8fc80d60f8c3571', deleted: { fingerprints: 1, apiCallLogs: 12 } } ``` ### Consent You are responsible for any consent the law requires where your visitors are, for example under the ePrivacy Directive and GDPR in the EU or PECR in the UK. TraceTail does not show a consent banner. Because the script collects nothing until you call `generateFingerprint()`, you can wait for consent before calling it: ```javascript const { likelyRequiresConsent } = TraceTail.getComplianceContext(); // hasFingerprintingConsent() stands for your consent manager. if (!likelyRequiresConsent || hasFingerprintingConsent()) { TraceTail.generateFingerprint().then(({ visitorId }) => { // Send visitorId to your backend. }); } ``` ### getComplianceContext() Hints worked out in the browser. They are not geolocation, and they never change what the SDK collects. - `gpcEnabled` boolean Global Privacy Control is on. - `doNotTrack` boolean Do Not Track is on. - `timezone` string The IANA time zone, such as Europe/Berlin. - `locale` string The browser language, such as de-DE. - `region` 'EU' | 'EEA' | 'UK' | 'US' | 'OTHER' Inferred from the time zone, then the locale. - `isEU, isEEA, isUK` boolean Shorthands for `region`; the EEA includes the EU. - `likelyRequiresConsent` boolean True when `isEEA`, `isUK`, `gpcEnabled` or `doNotTrack` is true. ## Troubleshooting ### The Content Security Policy blocks the SDK Allow the script and the API in your policy: ```text Content-Security-Policy: script-src 'self' https://tracetail.io; connect-src 'self' https://tracetail.io ``` Keyless pages only need `script-src`. With the npm package the SDK is in your bundle, so you only need `connect-src`, and only with a key. With a [first-party setup](https://tracetail.io/docs#first-party), `'self'` covers both. ### 401 DOMAIN\_MISMATCH The page's origin doesn't match the key's domain. Check the domain in Settings: `www.` and subdomains are covered, other domains are not. Use a `localhost` key for local development. Calls from servers, curl or scripts have no page origin and always get this error; from a server, use the [Server API](https://tracetail.io/docs#server-api). ### TraceTail is not defined Your code ran before the script. Put the script tag above your code, without `async` or `defer`. If you load it asynchronously, wait for its `load` event: ```html ``` ### Ad blockers and privacy extensions Some extensions block third-party scripts or requests. If the script is blocked, `window.TraceTail` is undefined, so check for it before calling. If only the API call is blocked, the SDK still returns the visitor ID, with `error.code` set to `NETWORK_ERROR`. A [first-party setup](https://tracetail.io/docs#first-party) serves both the SDK and the API from your own domain, which blockers that filter by domain leave alone. ### Requests don't show in the dashboard Check that the tag has `data-api-key` (without it the SDK runs keyless), then read `result.error`: its code says what went wrong (see [Errors](https://tracetail.io/docs#rest-api-errors)). `TraceTail.configure({ debug: true })` logs the details. ### Still stuck? Email with the page URL, your browser and the `requestId` or `error.code`. The [live demo](https://tracetail.io/live-demo) shows what a working integration returns in your browser. ## Migrating from FingerprintJS FingerprintJS is the open-source library from Fingerprint. TraceTail computes its own visitor ID, so existing FingerprintJS IDs don't carry over. Run both side by side for a while, then switch: 1. Add TraceTail next to your current integration (see [Install](https://tracetail.io/docs#install)). 2. Send both IDs with each event and store them together, for example for 30 days, so a returning visitor's records can be linked. 3. Move your lookups to the TraceTail `visitorId`, then remove FingerprintJS. **Before: FingerprintJS** ```typescript import FingerprintJS from '@fingerprintjs/fingerprintjs'; const fp = await FingerprintJS.load(); const { visitorId } = await fp.get(); ``` **After: TraceTail** ```typescript import { TraceTail } from '@tracetail/js'; const tracetail = new TraceTail({ apiKey: 'YOUR_API_KEY', endpoint: 'https://tracetail.io/api' }); const { visitorId } = await tracetail.generateFingerprint(); ``` | FingerprintJS | TraceTail | | ------------------------------------ | ------------------------------- | | `result.visitorId` | `result.visitorId` | | `result.confidence.score` (0 to 1) | `result.confidence` (0 to 1) | | `result.requestId` (Fingerprint Pro) | `result.requestId` (with a key) | For prices and features side by side, see [TraceTail vs FingerprintJS](https://tracetail.io/vs-fingerprintjs). --- Usage-based pricing # Pay only for what you use 1,000 identification requests free every month, with no credit card. After that, $0.10 per 1,000 requests: the same rate at every volume. - First 1,000 requests every month No credit card required Free - Every 1,000 requests after that Billed monthly once you add a card $0.10 [Get started free](https://tracetail.io/auth) Without a card, requests beyond the free 1,000 are declined until the next month begins. ## What you'd pay 10,000 requests a month $0.90 First 1,000 free, then 9 × $0.10 for the other 9,000 100,000 requests a month $9.90 First 1,000 free, then 99 × $0.10 for the other 99,000 1,000,000 requests a month $99.90 First 1,000 free, then 999 × $0.10 for the other 999,000 ## Everything is included There are no plans or feature tiers. Every account gets the full product. - 99.6% accuracy - 40+ browser signals per identification - The same ID in normal and incognito windows - Automated-browser flag (headless Chrome, WebDriver) - API keys locked to your domains - Dashboard with requests and visitors per key - Script tag or npm, under 10 KB gzipped - 100 requests per second per key - Email support ## Compared with Fingerprint Monthly cost at the same volume, using Fingerprint's published prices. | Requests a month | TraceTail | Fingerprint | | ---------------- | --------- | -------------------- | | 50,000 | $4.90 | $219.00 (Pro Plus) | | 500,000 | $49.90 | $2,019.00 (Pro Plus) | | 5,000,000 | $499.90 | Custom (Enterprise) | Fingerprint Pro Plus: $99.00 a month including 20,000 API calls, then $4.00 per 1,000. Source: [fingerprint.com/pricing](https://fingerprint.com/pricing/), checked October 4, 2026. [Full comparison](https://tracetail.io/vs-fingerprintjs) ## Questions - How does the free allowance work? Every account gets 1,000 identification requests free each calendar month (UTC), with or without a card. The allowance resets at the start of every month. - Do I need a credit card? No. Sign up and use 1,000 requests a month without one. Add a card in Settings → Billing only when you need more. - What happens after the free 1,000 requests? With a card on file, each 1,000 requests beyond the free 1,000 costs $0.10. Without a card, further requests that month are refused with HTTP 429 (QUOTA\_EXCEEDED) until the next month starts or you add one. - How am I billed? Monthly through Stripe, for the requests beyond your free allowance. The rate is $0.10 per 1,000 at every volume, with no plans, minimums or annual contracts. - What counts as a request? Each successful identification call your site makes with your API key. Visitor IDs computed without an API key stay in the browser, never reach TraceTail and are not counted. - Is there a rate limit? Yes: 100 requests per second per API key, to protect the service from abuse. Requests above it receive HTTP 429 with a Retry-After header. - Can I stop paying at any time? Yes. There is no contract. Cancel billing from Settings → Billing whenever you like and you keep the free 1,000 requests a month. ## Start with 1,000 free requests a month 1,000 requests free every month, no credit card required. Add a card only when you need more. [Get started free](https://tracetail.io/auth) [Read the docs](https://tracetail.io/docs) --- Comparison · checked October 4, 2026 # TraceTail vs FingerprintJS Fingerprint, the company behind the FingerprintJS library, and TraceTail both identify browsers without cookies. This is how they compare on price and limits, using Fingerprint's published pricing. ## Monthly cost | Requests a month | TraceTail | Fingerprint | | ---------------- | --------- | --------------------------- | | 50,000 | $4.90 | $219.00 (Pro Plus) | | 500,000 | $49.90 | $2,019.00 (Pro Plus) | | 5,000,000 | $499.90 | Custom pricing (Enterprise) | TraceTail: the first 1,000 requests each month are free, then $0.10 per 1,000. Fingerprint Pro Plus: $99.00 a month including 20,000 API calls, then $4.00 per 1,000. Source: [fingerprint.com/pricing](https://fingerprint.com/pricing/), checked October 4, 2026. ## Plans and limits | | TraceTail | Fingerprint | | ----------------------------- | --------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | | Free allowance | 1,000 requests every month | 1,000 API calls a month (Free plan) | | Paid usage | $0.10 per 1,000 requests beyond the free 1,000 | Pro Plus: $99.00 a month including 20,000 API calls, then $4.00 per 1,000. Enterprise: custom pricing | | Trial | None needed: the free allowance renews every month | 14-day trial of Pro Plus | | Rate limit | 100 requests per second per API key | 5 requests per second (Free and Pro Plus); custom on Enterprise | | Volume and annual discounts | None: one rate at every volume, billed monthly | Annual billing and discounts through sales | | Stored visitor data | Kept until 180 days after a visitor's last visit | Kept 30 days (Free and Pro Plus), 90 days (Enterprise) | | Uptime SLA | None | 99.9% (Enterprise) | | Platforms | Web browsers | Web, iOS and Android | | Server-side verification | Server API: look up an identification or a visitor with a secret server key | Server API: look up an identification with a secret API key | | Signals beyond the visitor ID | Automated-browser flag (headless Chrome, WebDriver) | Smart Signals such as VPN and browser-tamper detection | ## Which one fits ### Fingerprint is the better fit if you need - SDKs for native iOS and Android apps - A contractual uptime SLA - Server-side signals such as VPN or browser-tamper detection ### TraceTail is the better fit if you want - To start free, without a card or a trial clock - One price per request at every volume, with no plan to outgrow - More than 5 requests per second without an enterprise contract ## Switching from FingerprintJS Replace the library, create a TraceTail client with your API key, and keep sending visitorId to your backend. The two libraries compute different IDs, so each returning visitor gets a new ID once. **FingerprintJS** ```javascript import FingerprintJS from '@fingerprintjs/fingerprintjs'; const fp = await FingerprintJS.load(); const { visitorId } = await fp.get(); ``` **TraceTail** ```javascript import { TraceTail } from '@tracetail/js'; const tracetail = new TraceTail({ apiKey: 'YOUR_API_KEY', endpoint: 'https://tracetail.io/api' }); const { visitorId } = await tracetail.generateFingerprint(); ``` ## Start identifying visitors today 1,000 requests free every month, no credit card required. Add a card only when you need more. [Get started free](https://tracetail.io/auth) [See pricing](https://tracetail.io/pricing) --- Comparison · checked October 5, 2026 # FingerprintJS alternatives FingerprintJS is the best-known browser fingerprinting library. These are the tools developers compare it with, from open-source libraries that run in your pages to APIs that register identifications and let your server verify them, with prices from each vendor's published pages. ## At a glance | Tool | What it is | Free | Server-side verification | License | | ----------------- | ---------------------------------------------- | ------------------------------------------- | --------------------------------------------------------------------- | -------------------------------- | | TraceTail | Browser fingerprinting API (script tag or npm) | 1,000 requests a month; keyless use is free | Yes: Server API with secret server keys | Hosted service; MIT-licensed SDK | | Fingerprint (Pro) | Device intelligence platform | 1,000 web API calls a month | Yes: Server API | Commercial | | FingerprintJS | Open-source browser library | Free: runs in your pages | No: IDs are computed in the browser only | MIT (since 5.0.0) | | ThumbmarkJS | Open-source library with a paid API | Library free; API 1,000 calls a month | With the API, results are posted to your server afterwards; no lookup | MIT (library) | | ClientJS | Open-source browser library | Free: runs in your pages | No: IDs are computed in the browser only | Apache-2.0 | ## Monthly price by volume What each hosted API costs at list price for the identification requests a month shown. The open-source libraries cost nothing to run but have no API behind them. | API | 10,000 a month | 100,000 a month | 1,000,000 a month | | ----------------- | -------------- | --------------- | ------------------- | | TraceTail | $0.90 | $9.90 | $99.90 | | Fingerprint (Pro) | $99.00 | $419.00 | Custom (Enterprise) | | ThumbmarkJS | €15 + VAT | €100 + VAT | €1,000 + VAT | ## The tools ### TraceTail Browser fingerprinting API (script tag or npm) A stable visitor ID with 99.6% accuracy, the same in normal and incognito windows, without cookies. The SDK runs keyless with no sign-up; with a key, identifications are registered, checked for automation and verifiable from your server. 1,000 requests are free every month, then $0.10 per 1,000 at every volume. Choose it when you want an API with server-side verification at a per-request price that stays low at volume, for web apps. [Pricing](https://tracetail.io/pricing) ### Fingerprint (Pro) Device intelligence platform The company behind FingerprintJS. Its Pro Plus plan costs $99.00 a month including 20,000 API calls, then $4.00 per 1,000, with native iOS and Android SDKs and Smart Signals such as VPN and browser-tamper detection. Free and Pro Plus allow 5 requests a second and keep data 30 days. Choose it when you need native mobile SDKs, VPN or tamper signals, or an enterprise SLA, and the price fits. [Source](https://fingerprint.com/pricing/) ### FingerprintJS Open-source browser library The best-known library. It is MIT-licensed again since version 5.0.0 (October 22, 2025); 4.x was BUSL-1.1. Its README says its accuracy is "significantly lower than in the commercial version" and that, being computed in the browser, "they are vulnerable to spoofing and reverse engineering". Choose it when you only need a rough in-browser hash, with nothing to verify on a server. [Source](https://github.com/fingerprintjs/fingerprintjs) ### ThumbmarkJS Open-source library with a paid API Its README puts the browser library at \~80% uniqueness and its API at \~99%. The Pro API plan costs €15 a month including 15,000 calls, then €1 per 1,000, excluding VAT. Choose it when you want an MIT library you can run yourself, with an optional API priced in euros. [Source](https://www.thumbmarkjs.com/pricing) ### ClientJS Open-source browser library Hashes browser traits, including the user agent, into a 32-bit number. Its last release is 0.2.1, from October 25, 2021. Choose it when you maintain a legacy integration that already depends on it. [Source](https://github.com/jackspirou/clientjs) ## Questions - Is FingerprintJS free? The FingerprintJS library is free and open source: MIT since version 5.0.0, released October 22, 2025 (versions 4.x were BUSL-1.1). Fingerprint, the company behind it, sells a separate commercial service whose Pro Plus plan starts at $99.00 a month. - What is the difference between FingerprintJS and Fingerprint Pro? FingerprintJS computes a hash in the browser and nothing else; Fingerprint's commercial product adds server-side processing, a Server API and Smart Signals. Fingerprint's README for the library says its accuracy is "significantly lower than in the commercial version". - Which FingerprintJS alternative costs least at volume? Of the hosted APIs compared here, at their published prices: 100,000 requests a month cost $9.90 on TraceTail, €100 plus VAT on ThumbmarkJS Pro and $419.00 on Fingerprint Pro Plus. The open-source libraries cost nothing but have no API behind them. - Can I check a visitor ID on my server? With an API, yes: TraceTail's Server API and Fingerprint's Server API both let your backend look up an identification before trusting it. Libraries that only run in the browser can't: the ID comes from the visitor's browser, so anyone can send any value. - How hard is it to switch from FingerprintJS to TraceTail? Replace the library with the TraceTail script tag or the @tracetail/js package and keep sending visitorId to your backend. The two compute different IDs, so each returning visitor gets a new ID once; run both side by side for a few weeks to link them. List prices from each vendor's pricing page as of October 5, 2026 (ThumbmarkJS's exclude VAT). Accuracy and uniqueness figures are each vendor's own claims. ## Start identifying visitors today 1,000 requests free every month, no credit card required. Add a card only when you need more. [Get started free](https://tracetail.io/auth) [TraceTail vs FingerprintJS](https://tracetail.io/vs-fingerprintjs)