Whether you're launching a feature-rich e-commerce site, building a virtual try-on experience, or powering a scalable AI engine, our developer-first architecture makes it easy to implement, customize, and deploy powerful AR and AI tools into your digital stack.
We offer flexible, modular solutions that can plug directly into your infrastructure, no heavy lifting required.
RESTful APIs & Webhooks
Pre-built SDKs for web & mobile
Clean, well-documented codebase
Full tech support & integration guides
Our platform is built to fit yours. Novagates works across all major environments, frameworks, and CMS platforms, from Shopify and Magento to custom-built systems.
Frontend agnostic
Headless architecture ready
Easy plugin options for CMS & storefronts
1
4
Your backend signs a client_assertion with its ES256 private key and POSTs it to /api/token, receiving a short-lived (5 min) access_token in return. You hand that to your frontend, which appends it to the SDK <script> tag as ?sessionToken=…. The SDK reads it off its own src and attaches it as Authorization: Bearer on every Novagates call.
Keep server-side only
Safe to ship to the browser
Refresh by re-fetching from your backend before the 5-minute expiry. Never send the private key or client_assertion to the browser.
Install
# Requires bash, curl, jq.# Pre-sign client_assertion with one of the language tabs below.
Implementation
CLIENT_ASSERTION="$(node ./scripts/sign-client-assertion.js)"curl -sS -X POST https://novagates.com/api/token \-H 'Content-Type: application/json' \-d "{\"client_assertion\": \"${CLIENT_ASSERTION}\",\"client_assertion_type\": \"urn:ietf:params:oauth:client-assertion-type:jwt-bearer\"}" | jq -r .access_token
POST /api/token — Request & response schema
The only endpoint your backend calls directly. Everything else is invoked by the SDK on your behalf using the sessionToken you embed on its <script> tag.
Request
POST · application/json or application/x-www-form-urlencoded
client_assertion (required) — ES256-signed JWT. Header carries the public jwk (kty/crv/x/y). Payload: iss=sub=[API_KEY], aud=https://novagates.com/api/token (exact match), unique jti.client_assertion_type (required) — must be exactly: urn:ietf:params:oauth:client-assertion-type:jwt-bearerdpop_jkt (optional) — base64url thumbprint (42–44 chars) of an ephemeral browser key to bind the token to.Response
200 OK · application/json
access_token — HS256-signed JWT. Claims: sub=[API_KEY], cnf.jkt=bound thumbprint, iss=novagates, unique jti.token_type — "Bearer"expires_in — 300 (5 minutes)Errors: 400 for invalid input, 401 when the header jkt doesn't match the stored [API_KEY] jkt, when aud is missing or isn't exactly the /api/token URL, or when the jti has been replayed.
Initialize the SDK by embedding a sessionToken on its <script> tag. Your frontend fetches the token from your backend (which calls /api/token), injects the SDK script tag with ?sessionToken=… and ?module=…, and the SDK reads it off its own src and attaches it as Bearer on every Novagates call.
Init flow
Implementation
<!doctype html><html lang="en"><head><meta charset="utf-8" /><title>Novagates Virtual Try-On</title></head><body><div id="novagates-root"></div><script>(async function bootNovagates() {// 1. Mint a short-lived sessionToken on YOUR backend.const res = await fetch("/api/sdk-token", { credentials: "include" });if (!res.ok) throw new Error("Failed to fetch SDK session token");const { sessionToken } = await res.json();// 2. Inject the SDK loader with sessionToken + module on the URL.const params = new URLSearchParams({sessionToken: sessionToken,module: "virtual-try-on",});const script = document.createElement("script");script.src ="https://cdn.novagates.com/sdk/novagates-sdk.js?" + params.toString();script.async = true;script.onload = function () {// 3. Init once the loader has registered the global.window.Novagates.init({rootId: "novagates-root",productsSource: "/api/products.json",immediate: true,});};document.body.appendChild(script);})();// 4. Listen for product events emitted by the SDK.window.addEventListener("sdk:addToCart", function (e) {console.log("[novagates] add to cart", e.detail);// forward to your cart, analytics, etc.});</script></body></html>
The SDK runs inside your page, so it is governed by your site's Content Security Policy, not ours. If you enforce a strict CSP, allow the directives below before you go live - otherwise the SDK fails in ways that are hard to attribute from the outside: a black iframe, or a CompileError thrown from inside a minified bundle.
A srcdoc iframe does not isolate the SDK from your policy
The SDK runs in an about:srcdoc frame, and such a frame has no URL to derive a policy from - it inherits the embedding document's CSP, and its origin. Assuming the iframe isolates the SDK from your policy is the single most common misunderstanding here, and it is wrong.
| Directive | Sources to allow | Why |
|---|---|---|
script-src | 'self' https://cdn.novagates.com | The loader, the module bundles and the ONNX Runtime .mjs are all script src off the CDN. |
script-src | 'wasm-unsafe-eval' | MediaPipe, ONNX Runtime and TFLite all call WebAssembly.compile / instantiate, which CSP gates. Without it the SDK loads and then fails from inside with a CompileError - no blocked-resource message, so it is the hardest failure to attribute. 'unsafe-eval' also works but is strictly wider and should not be used. |
connect-src | 'self' blob: https://cdn.novagates.com | Model weights (.nova), MediaPipe .task landmarkers, .wasm runtimes, .glb / .hdr scene assets and the category manifests are all fetched. TFLite weights are decoded into a Blob and re-fetched through an object URL, which is why blob: belongs here too. |
worker-src | 'self' blob: https://cdn.novagates.com | The inference worker is bundled inline and instantiated from a blob: URL. Omit this directive and it does NOT fall back to default-src - the chain is child-src, then script-src, then default-src - so it silently inherits whatever script-src allows. The refusal itself does reach the console and the violation report - it is the SDK's fallback to main-thread inference that is silent, so the page keeps working and only gets slower. |
media-src | 'self' blob: data: | Uploaded and recorded video plus generated audio play through URL.createObjectURL. The live-camera path uses srcObject, which CSP does not gate - so camera try-on keeps working while upload try-on breaks, which is a nasty way to find out. |
style-src | 'self' 'unsafe-inline' | The SDK renders into your page and styles its own UI inline. Absent a style-src it falls back to default-src, so a strict policy renders the try-on surface unstyled rather than blocking it — visibly wrong, with nothing thrown. This is the value our own site serves, in Report-Only, while running the same SDK; if yours is stricter, prove it in Report-Only before promoting. Be clear-eyed that this one is a real relaxation of your policy and the only directive here that is: 'unsafe-inline' on style-src permits any injected CSS on your pages, so it is worth confirming you want it rather than pasting it. A nonce or hash works for styles too if you would rather not, at the cost of coordinating with each SDK release. |
font-src | 'self' data: | Icon and UI fonts the SDK inlines as data: URIs. Falls back to default-src when omitted, which drops them to the browser default — again visible, not thrown. |
img-src | 'self' data: blob: https://cdn.novagates.com | Canvas captures the SDK produces as data: and blob: URLs, plus the image assets it loads from the origin it is itself served from. Your PRODUCT imagery is not covered here — that comes from whatever origin your catalog points at, so add that origin yourself. This is also the one row not derived from our own middleware policy: that file allows the same origin in img-src for an unrelated reason (our marketing media CDN shares it), so it corroborates this row rather than proving it. If you would rather not grant it, drop it and watch the Report-Only reports. |
The bootstrap above is an inline script — give it a nonce
The Vanilla JS example in the integration section above puts its boot function in an inline script tag on your page. Under a strict script-src that tag is blocked and the SDK never loads at all — this is the first thing anyone hits who copies both the snippet and the policy. Emit a per-request nonce on the tag and in the header: add a nonce attribute to the opening script tag of the Vanilla JS example above, with the same value you substitute for the nonce placeholder in the snippets below. Or move the function into a file that 'self' already covers, which needs no nonce at all. The example is left without a nonce attribute on purpose, because the value has to be generated per request and a copied placeholder would be worse than none. Do not reach for 'unsafe-inline': it re-enables every inline script on your site, which is a far higher price than this needs. AND MIND WHAT THE NONCE DOES TO A POLICY YOU ALREADY HAVE — under CSP Level 3, a script-src containing a nonce or hash makes 'unsafe-inline' in that same directive IGNORED. If you serve script-src 'self' 'unsafe-inline' today and simply append this nonce, every other inline script on your storefront (tag manager, analytics, chat widget) stops running, site-wide, with nothing on this page to warn you. Nonce them all in the same change, or externalise this one function instead.
# THESE ARE THE SDK'S DIRECTIVES, NOT A COMPLETE POLICY. Merge each source# expression into the directive you already serve - pasting this block as your# whole CSP replaces yours and breaks the rest of your site.## NOTE on the nonce: a script-src that contains a nonce makes 'unsafe-inline'# in that same directive IGNORED (CSP Level 3). If you serve 'unsafe-inline'# today, nonce your other inline scripts in the same change or they stop# running.## Roll it out FIRST in Report-Only, with a collector at /csp-report, and read# the reports before you promote. Shown wrapped for readability - send it as# ONE header line; HTTP header folding is obsolete and proxies will not# reassemble it.add_header Content-Security-Policy-Report-Only "script-src 'self' 'wasm-unsafe-eval' https://cdn.novagates.com 'nonce-<PER-REQUEST>';connect-src 'self' blob: https://cdn.novagates.com;worker-src 'self' blob: https://cdn.novagates.com;style-src 'self' 'unsafe-inline';font-src 'self' data:;media-src 'self' blob: data:;img-src 'self' data: blob: https://cdn.novagates.com;report-uri /csp-report;report-to csp-endpoint" always;# report-to needs this companion header to bind the group name to a URL.add_header Reporting-Endpoints 'csp-endpoint="https://your-site.example/csp-report"' always;
Check connect-src if your integration calls the REST API from the browser. The values above cover what the SDK fetches from the CDN and from your own origin; the token exchange at novagates.com runs on your backend, where CSP does not apply. If you add a browser-side call to that API yourself, allow its origin in connect-src too.
Start Report-Only, then promote
Ship the policy on Content-Security-Policy-Report-Only with a reporting endpoint first, and leave it there for a full traffic cycle before promoting it to enforcing. That is the same ordering we hold ourselves to on this site.
Declare both report-uri and report-to
report-uri is deprecated in the spec but is the only one Firefox and Safari implement, while Chrome and Edge ignore it entirely whenever report-to is present. Declaring only one collects nothing from a whole browser family - which looks identical to having no violations.
No frame-src entry is needed for the SDK: an about:srcdoc frame is not gated by that directive because it inherits instead. That changes only if a future release moves the SDK to a real cross-origin URL.
For WordPress / WooCommerce, Magento, Shopify, and OpenCart we ship the full plugin. It handles client_assertion signing, /api/token exchange, sessionToken rotation, and catalog sync for you — no signing keys to manage. Download links and installation instructions live inside your dashboard.
The SDK consumes a JSON catalog conforming to the schema below. The `technologies` field decides which try-on / analysis / 3D pipeline activates per product; `product_types` and `category` drive the UX.
simple
Single SKU, one set of attributes
configurable
Parent SKU exposes variants (shades, sizes)
variable
Variants with independent pricing
// GET /api/products → Product[]//// Three product shapes, distinguished by `type_id`:// "simple" — single SKU, single set of attributes// "configurable" — parent SKU exposes child variants (size/shade swatches)// "variable" — parent SKU with priced variants (e.g. fragrance volumes)//// Every product family below extends this base. Required vs null fields// depend on the family — see the next tabs.interface Product {/* identity */id: number;uid: string;sku: string; // unique merchant SKUtype_id: "simple" | "configurable" | "variable";name: string;brand: string | null;manufacturer: string | null;country_of_manufacture: string | null;made_in: string | null;/* taxonomy */category: string; // e.g. "Women > Makeup > Face > Foundation"product_types: string; // see family tabsproduct_type_id: string; // your CMS taxonomy id/* commerce */url_key: string;url_path: string;price: { regularPrice: { amount: { currency: string; value: number } } };/* media */image: { url: string };swatch_image: { url: string };thumbnail: { url: string };small_image?: { url: string };/* try-on / 3D pipeline switch */is_product_try_on: 0 | 1;is_product_skin_improvement: 0 | 1;asset_url: string | null; // PNG texture, .glb, or null per familytechnologies: string; // pipe-separated; activates SDK modules/* configurable / variable parents */variants?: Array<{ product: Product }>;}
Novagates isn’t just a platform, it’s a partner.
We support developers every step of the way, with expert onboarding, shared codebases, and a growing developer community.