glassprint · Quickstart

Device intelligence in 10 minutes

Add the glassprint engine to your backend and it auto-mounts every endpoint and runs the full identification pipeline in-process — no separate server to deploy. Then add the browser client, call identify(), and read a typed verdict: human, declared agent, undeclared automation, or unknown.

The shape

How the flow fits together

glassprint is server-authoritative (Archetype B): the browser client only reads signals behind a fail-closed consent gate and seals them to youringest endpoint. The embedded engine in your backend decrypts, adds network-tier signals the browser can't see, resolves identity, and returns the typed result. The visitorId is generated in-process and is tenant-bound — it is never computed in the page.

1 · Your backend

Embed the engine

Add one library dependency. The engine auto-mounts all glassprint endpoints — ingest, pubkey, me, privacy-rights — and manages storage and verdict processing in-process. No separate server to run.

2 · Browser

Collect & seal

The MIT client reads each probe whose purpose your consent engine has granted, seals the bundle (AES-256-GCM, or HPKE/ECDH-P256), and POSTs it to your own ingest endpoint.

3 · In-process verdict

Act on the result

Your backend already has the typed verdict in-process. Read class, visitorId, and suspectScore — allow, step-up, or review.

Step 1

Embed the glassprint engine in your backend

The primary integration model: add the embeddable engine library to your own backend. It ships for Node (Express / Fastify), Go (net/http), Python (FastAPI / Flask), and Java (Spring Boot starter + framework-agnostic core). Adding the dependency auto-provides the ingest, pubkey, me, and privacy-rights endpoints plus storage and verdict processing — all in-process.

The engine libraries are commercial / source-available (UNLICENSED), not MIT — they are the product moat. The client SDKs (browser, iOS, Android, React Native) are MIT. A commercial license is required for production use; contact us to get access.

Node — Express

shell
npm i @noidme/glassprint-engine express
server.ts
import express from 'express';
import { createEngine } from '@noidme/glassprint-engine';
import { glassprintEngine } from '@noidme/glassprint-engine/express';

const engine = await createEngine({
  storage: { driver: 'sqlite', path: './glassprint.db' }, // or 'postgres'
  kids: ['acme'],
  masterSecret: process.env.GLASSPRINT_MASTER_SECRET,
});

express().use(glassprintEngine(engine)).listen(8080);
// Now serving: GET /_fp/pubkey  POST /_fp/i/v1  POST /me  POST /privacy/*

Node — Fastify

server.ts
import Fastify from 'fastify';
import { createEngine } from '@noidme/glassprint-engine';
import { glassprintFastify } from '@noidme/glassprint-engine/fastify';

const engine = await createEngine({ storage: { driver: 'sqlite', path: './glassprint.db' }, kids: ['acme'] });
const app = Fastify();
app.register(glassprintFastify, { engine });
await app.listen({ port: 8080 });

Go

shell
go get github.com/noidme/glassprint-engine-go
main.go
import (
    glassprint "github.com/noidme/glassprint-engine-go"
    "net/http"
    "log"
)

eng, err := glassprint.New(glassprint.Config{
    MasterSecret: os.Getenv("GLASSPRINT_MASTER_SECRET"),
    Kids:         []string{"acme"},
})
if err != nil { log.Fatal(err) }

http.Handle("/", eng.Handler()) // auto-mounts all glassprint endpoints
log.Fatal(http.ListenAndServe(":8080", nil))

Python — FastAPI

shell
pip install "glassprint-engine[fastapi]"
main.py
from fastapi import FastAPI
import glassprint_engine as gp

engine = gp.create(storage="sqlite:///glassprint.db", kids=["acme"])
app = FastAPI()
app.include_router(gp.asgi.router(engine))
# uvicorn main:app

Java — Spring Boot starter

Add the starter dependency to your pom.xml and set two properties. Every endpoint is auto-registered via Spring Boot auto-configuration — zero manual wiring.

pom.xml
<dependency>
  <groupId>com.noidme</groupId>
  <artifactId>glassprint-engine-spring-boot-starter</artifactId>
  <version>0.1.0</version>
</dependency>
application.properties
glassprint.master-secret=your-at-least-32-char-production-secret
glassprint.kids=acme
# optional: glassprint.jdbc-url=jdbc:postgresql://localhost/glassprint
Step 2

Install the browser client

The browser client is @noidme/glassprint— MIT-licensed, ships ESM + CJS + types, and stays under a 30 KB gzipped budget. You can audit exactly what it collects because the collection code runs in your page.

shell
npm i @noidme/glassprint

Point the client at the endpoint your embedded engine is now serving — your own domain, no external dependency.

client.ts
import { createGlassprint, createSealedTransport } from '@noidme/glassprint';

const transport = createSealedTransport({
  key,                                        // your tenant CryptoKey, or use HPKE: fetch /_fp/pubkey
  deliver: async (sealed) =>
    (await fetch('https://id.acme.com/_fp/i/v1', {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify(sealed),           // { kid, iv, ct, epk? }
    })).json(),
});

const gp = createGlassprint(
  { kid: 'acme', endpoint: 'https://id.acme.com/_fp/i/v1' },
  transport,
);
The no-argument form of createGlassprint has no transport, so identify() resolves with reason: 'transport-error' and visitorId: null — it never mints an id until you wire delivery. A working integration must supply a transport that seals the payload and POSTs it to your endpoint.
Step 3

Call identify()

identify() always resolves and never throws — on consent denial, a missing engine, no signals, or a transport failure it still returns a typed result you can branch on. The optional tag is free-form and echoed back so you can correlate the call to a place in your flow (e.g. checkout, login).

client.ts
const r = await gp.identify({ tag: 'checkout' });
Step 4

Read the typed result

The result is a verdict, not a bare id. Read classto know what you're looking at, visitorId for the device, and suspectScore as additive risk data. class is one of four values:

classMeaning
humanAssessed as a real person — above the confidence floor, no human-implausible picture.
declared_agentA bot that declared itself and verified via Web Bot Auth (RFC 9421 Ed25519). Declared is recorded, but still scored — authenticated is not the same as trusted.
undeclared_automationAutomation that did not declare itself, with a human-implausible signal picture (e.g. multiple strong tells). Never emitted at high confidence behind a CDN.
unknownBelow the confidence floor — the server refuses to guess. A lone signal (one webdriver flag, one software GPU) lands here, which is also how privacy users are protected from a false bot label.

The companion fields:

FieldTypeWhat it is
visitorIdstring | nullEngine-minted, salted-HMAC, epoch-rotated, and tenant-bound (the same device is distinct per customer — no cross-customer supercookie). null when no id could be minted (e.g. transport-error, consent-denied).
suspectScorenumber?Additive risk in [0,1] from inconsistencies and automation tells. Data for your risk engine — never a verdict to act on blindly. A recognised privacy defence (Brave/Tor farbling) caps it.
reasonIdentifyReasonok · consent-denied · consent-engine-absent · no-signals · transport-error — branch on this before trusting visitorId.
client.ts
const r = await gp.identify({ tag: 'checkout' });

if (r.reason !== 'ok') {
  // consent-denied | consent-engine-absent | no-signals | transport-error
  // r.visitorId is null — fall back to your unauthenticated path.
} else if (r.class === 'undeclared_automation') {
  // additive risk signal — step up or review
} else {
  // r.class is 'human' | 'declared_agent' | 'unknown'
  // The verdict is already in your backend in-process — act on it directly.
  send(r.visitorId, r.class, r.suspectScore);
}
Step 5

Act on the verdict — it is already in your backend

Because the engine runs in-process, the verdict is available server-side the moment the ingest request is processed — no round-trip required. Your risk logic can act on it directly from the same process. The ingest surface returns the same typed payload over HTTP, with status codes you can key on:

StatusReasonMeaning
POST200okIdentified — act on the verdict.
POST451consent-missingNo lawful basis to identify — the device-identification purpose was not granted. Lawful by construction.
POST409replayThe replay nonce was already claimed.
POST429rate-limitedBack off — a Retry-After header is set.

On your backend, treat the verdict as one input among your signals:

backend (pseudo)
// Your backend — the verdict arrives via the embedded engine in-process:
async function onCheckout(req) {
  const { class: klass, visitorId, suspectScore } = req.body;

  if (klass === 'undeclared_automation') return stepUp();   // challenge
  if (suspectScore && suspectScore > 0.6)  return review();   // hold for review
  if (klass === 'declared_agent')          return agentPath(); // a known good bot

  return allow(visitorId); // human / unknown above your own threshold
}
Embed ships today: the engine runs inside your own perimeter, so signals never leave it. The standalone glassprint-server (Docker / Helm / Terraform) is the alternative for polyglot shops or teams that prefer a separate service. The trained-ML fuzzy linker is roadmap — the live identity path is exact-match. The cross-browser deviceId field is reserved and returns null today. The managed-SaaS option is design-partner onboarding, not open self-signup.
Recap

The ten-minute path

  1. Add @noidme/glassprint-engine (Node), glassprint-engine-go (Go), glassprint-engine[fastapi] (Python), or the Spring Boot starter (Java) to your backend.
  2. The engine auto-mounts all glassprint endpoints (ingest, pubkey, me, privacy-rights) in-process — no separate server.
  3. Install @noidme/glassprint (browser client, MIT).
  4. Create the client with your kid and endpoint, and wire a sealed transport (without it, identify() returns transport-error / visitorId: null).
  5. Call await gp.identify({ tag }) behind your consent engine.
  6. Read r.class, r.visitorId and r.suspectScore — the verdict is already in your backend in-process.

Run it inside your own perimeter

Embed the glassprint engine in your own backend so your fraud signals never leave your network, then feed the typed verdict into the risk engine you already run.