Embedded Analytics Developer Guide
Version 0.4.4 | Last Updated: July 2026
This is the starting point for building against QuantumLayers as a third-party developer. It explains what QL exposes, where its client-side libraries live, how requests are authenticated, and the two ways to reach QL’s facilities — raw API calls or the bundled JavaScript Development Kit (JDK). If you already know which path you want, jump to Embedded Analytics Developer Guide (API) for the full endpoint reference or Embedded Analytics Developer Guide (JDK) for the full class/function reference.
Table of Contents
Overview
QuantumLayers is a standalone analytics platform built around a few core facilities, all of which are reachable from outside the QL server:
- Datasets — CSV upload, database/API connections, Google Sheets and SFTP sources, dataset merging/joins, and per-column statistics computed at ingestion time.
- Charting — a library of Chart.js-compatible chart generators (bar, line, pie, scatter, box plot, heatmap, regression, and more) driven purely by POST parameters, plus an auto-recommendation engine that ranks the most informative charts for a given dataset.
- Statistical analysis — summary statistics, correlation, PCA, and distribution analysis, backed by the Python scripts in
python/. - AI insights & agent — rule-based and Claude-powered dataset insights, plus a multi-turn conversational agent that can query and chart a dataset on request.
- Scheduled reports, organizations/teams, and billing — recurring report delivery, multi-seat org management, and subscription plans that gate the above features.
Every one of these facilities is exposed the same way: a WordPress AJAX endpoint (an action name) secured with Bearer token authentication. There is no separate REST API and no GraphQL layer — action names are the entire surface area.
Where the JavaScript lives
Every client-side QL module is published as a standalone file under https://quantumlayers.com/jdk/ and can be pulled into any page with a plain <script> tag — no build step, bundler, or npm package required:
https://quantumlayers.com/jdk/auth.js // QLAuth — required by every other module
https://quantumlayers.com/jdk/upload.js // QLUpload — CSV upload / dataset editing
https://quantumlayers.com/jdk/dashboard.js // QLDashboard — dataset inventory, lifecycle actions
https://quantumlayers.com/jdk/dataset-detail.js // QLDatasetDetail
https://quantumlayers.com/jdk/analytics.js // QLAnalytics — chart rendering, stats, saved charts
https://quantumlayers.com/jdk/insights.js // QLInsights — AI-powered dataset insights
https://quantumlayers.com/jdk/merge-datasets.js // QLMergeDatasets
https://quantumlayers.com/jdk/report-scheduler.js // QLReportScheduler
https://quantumlayers.com/jdk/organizations.js // QLOrganizations
https://quantumlayers.com/jdk/agent.js // QLAgent — multi-turn AI agent
auth.js has no dependency on any other QL module, but every other module depends on it (they all call QLAuth.getSessionToken() to build their Authorization header), so it must always be the first QL script loaded on the page, after jQuery. See the JDK guide for the full function/class reference of each module.
How AJAX/API endpoints are invoked
There is a single URL for every endpoint. You select the operation via the action POST field, and authenticate via the Authorization header (see Authentication below):
POST https://quantumlayers.com/wp-admin/admin-ajax.php
Content-Type: application/x-www-form-urlencoded
Authorization: Bearer <token>
action=ql_get_chart_data&dataset_id=42&chart_type=bar&...
Every endpoint responds with the standard WordPress AJAX envelope — { "success": true, "data": {...} } on success, or { "success": false, "data": { "message": "..." } } on failure. Public endpoints (callable by unauthenticated third parties, e.g. viewing a public dataset’s chart) skip nonce checks entirely and rely only on ql_try_auth()/ql_verify_auth() for the Bearer token; endpoints called from within a browser session on the QL site itself additionally expect a nonce field, which is only meaningful for same-origin requests and is not required for cross-origin/API use.
Prerequisites
You need access to a valid QuantumLayers account. The third-party page does not need to sit on the QL server itself — it only needs network access to the QL server.
QL JavaScript Libraries
The following file can be accessed directly from the QL host and can be loaded with a <script> tag:
- jdk/auth.js — exposes the
QLAuthobject: token storage, and sign-in helpers.
<script src="https://quantumlayers.com/jdk/auth.js"></script>
If you’re only calling endpoints directly (no JDK modules), auth.js is optional — see Authentication for how to obtain a Bearer token without it.
Chart.js Setup
QuantumLayers uses Chart.js 4.4.0 and three plugins. Load all four CDN scripts before your own code if you intend to render the chart configs returned by the analytics endpoints:
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@sgratzl/chartjs-chart-boxplot@4.2.5/build/index.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@3.0.0/dist/chartjs-adapter-date-fns.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-chart-matrix@2.0.1/dist/chartjs-chart-matrix.min.js"></script>
The box-plot, date-adapter, and matrix plugins are only needed if you render box_plot/violin, time-based, or heatmap charts respectively — see Analytics & Charting Endpoints for the full chart type reference.
Authentication
Every AJAX request must carry a Bearer token in the Authorization header. Three approaches are available, described below with full code examples. For the raw endpoint invocation (params only, no explanation), see the condensed Authentication endpoints section of the API guide.
Option A — QL Session Token (Same-Origin)
After the user signs in through QL, their token is in localStorage under the key ql_session_token. Read it with QLAuth.getSessionToken():
$.ajax({
url: 'https://quantumlayers.com/wp-admin/admin-ajax.php',
method: 'POST',
headers: { 'Authorization': 'Bearer ' + QLAuth.getSessionToken() },
data: { action: 'ql_get_chart_data', dataset_id: 42, chart_type: 'bar' }
});
This option only works for pages served from the same browser context where the user has already logged into QL — it relies on that browser’s localStorage already holding a valid token. It’s the simplest option when you’re embedding QL on a page that lives under quantumlayers.com itself, or when you fully control a flow that lands the user on a QL login page first.
Option B — Third-Party Provider Registration (Shadow Users)
For cross-origin embeds, the QL operator registers your application in the ql_auth_providers table with three fields:
- id — unique slug (e.g.
"acme-corp"); becomes thekidin the JWT header. - name — human-readable label.
- jwt_secret — shared HMAC-SHA256 secret (HS256).
Your backend mints a signed HS256 JWT with the following structure, then POSTs it to ql_third_party_signin. sub and email are required; first_name and last_name are optional but kept in sync on every login.
// Header — kid must match the id column in ql_auth_providers
{ "alg": "HS256", "typ": "JWT", "kid": "<provider_id>" }
// Payload
{ "sub": "<stable_user_id>", "email": "jane@example.com",
"first_name": "Jane", "last_name": "Doe",
"exp": <unix timestamp> }
Minting the JWT — PHP
With firebase/php-jwt (composer require firebase/php-jwt):
use Firebase\JWT\JWT;
$provider_id = 'acme-corp'; // matches ql_auth_providers.id
$jwt_secret = 'your-shared-secret'; // matches ql_auth_providers.jwt_secret
$payload = [
'sub' => 'user-123', // stable unique ID in your system
'email' => 'jane@example.com',
'first_name' => 'Jane',
'last_name' => 'Doe',
'exp' => time() + 3600, // 1-hour expiry
];
// Fourth argument sets the kid header field
$jwt = JWT::encode($payload, $jwt_secret, 'HS256', $provider_id);
Without an external library (PHP built-ins only):
function base64url_encode(string $data): string {
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
$provider_id = 'acme-corp';
$jwt_secret = 'your-shared-secret';
$header = base64url_encode(json_encode(['alg' => 'HS256', 'typ' => 'JWT', 'kid' => $provider_id]));
$payload = base64url_encode(json_encode([
'sub' => 'user-123',
'email' => 'jane@example.com',
'first_name' => 'Jane',
'last_name' => 'Doe',
'exp' => time() + 3600,
]));
$signature = base64url_encode(hash_hmac('sha256', "$header.$payload", $jwt_secret, true));
$jwt = "$header.$payload.$signature";
Minting the JWT — Node.js
With jsonwebtoken (npm install jsonwebtoken):
const jwt = require('jsonwebtoken');
const PROVIDER_ID = 'acme-corp'; // matches ql_auth_providers.id
const JWT_SECRET = 'your-shared-secret'; // matches ql_auth_providers.jwt_secret
const token = jwt.sign(
{
sub: 'user-123', // stable unique ID in your system
email: 'jane@example.com',
first_name: 'Jane',
last_name: 'Doe',
},
JWT_SECRET,
{
algorithm: 'HS256',
keyid: PROVIDER_ID, // sets the kid header field
expiresIn: '1h',
}
);
Without an external library (Node.js built-in crypto):
const crypto = require('crypto');
const PROVIDER_ID = 'acme-corp';
const JWT_SECRET = 'your-shared-secret';
function base64url(str) {
return Buffer.from(str).toString('base64')
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}
const header = base64url(JSON.stringify({ alg: 'HS256', typ: 'JWT', kid: PROVIDER_ID }));
const payload = base64url(JSON.stringify({
sub: 'user-123',
email: 'jane@example.com',
first_name: 'Jane',
last_name: 'Doe',
exp: Math.floor(Date.now() / 1000) + 3600,
}));
const sig = crypto.createHmac('sha256', JWT_SECRET)
.update(`${header}.${payload}`)
.digest('base64')
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
const token = `${header}.${payload}.${sig}`;
Once you have the signed token, POST it to QL. You can either call the endpoint directly:
$.post('https://quantumlayers.com/wp-admin/admin-ajax.php',
{ action: 'ql_third_party_signin', jwt: SIGNED_JWT },
function(res) {
if (res.success) QLAuth.setSessionToken(res.data.session_token);
});
…or, if auth.js is loaded, use the QLAuth.thirdPartySignin() helper, which wraps the same request and stores the returned token for you:
QLAuth.thirdPartySignin(SIGNED_JWT,
function(user) { console.log('signed in as', user.email); },
function(err) { console.error('sign-in failed', err); });
QL verifies the signature, then finds or creates a shadow user identified by provider_id + provider_sub. Email and name are kept in sync on every login. The response contains a standard session_token for all subsequent requests.
Note: ql_try_auth() allows unauthenticated access for public datasets; ql_verify_auth() requires a valid token and is used by all write and analysis endpoints.
Option C — API Token (Long-Lived / Server-to-Server)
API tokens created on the API Tokens page can be used directly as Bearer tokens — no prior call to ql_third_party_signin or any other sign-in endpoint is required. This makes them the simplest option for server-side scripts, CI/CD pipelines, and backend integrations where an interactive login flow is impractical.
To create a token, go to the API Tokens page in your account, click + Create token, give it a name and an optional expiry, and copy the value shown immediately after creation — it is only displayed once.
Once you have the token, pass it as the Authorization header on every request:
// Node.js (fetch)
const API_TOKEN = 'qlat_xxxxxxxxxxxxxxxx'; // token from the API Tokens page
const res = await fetch('https://quantumlayers.com/wp-admin/admin-ajax.php', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_TOKEN}`,
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
action: 'ql_get_dashboard_data',
}),
});
const json = await res.json();
console.log(json.data.datasets);
# curl
curl -s -X POST https://quantumlayers.com/wp-admin/admin-ajax.php \
-H "Authorization: Bearer qlat_xxxxxxxxxxxxxxxx" \
-d "action=ql_get_dashboard_data"
Choosing between the three options:
| Option | Best for | Token lifetime |
|---|---|---|
| A — QL Session Token | Pages hosted on the same QL origin where the user is already signed in | 30-day session (refreshed on activity) |
| B — Third-party JWT | Cross-origin embeds where your backend needs to provision shadow users automatically | Exchanged for a 30-day session token |
| C — API Token | Server-side scripts, CI/CD pipelines, backend integrations — no browser involved | Configurable: 30 days / 90 days / 1 year / never |
Accessing QL Facilities
Once you can authenticate, there are two ways to reach the facilities described in Overview. Both ultimately hit the same admin-ajax.php endpoints — the JDK is a convenience wrapper, not a different transport.
Direct API / AJAX Calls
Call action endpoints directly with any HTTP client — $.ajax(), fetch(), curl, or a server-side HTTP library in any language. This is the right choice when you don’t want to load any QL JavaScript at all, e.g. server-to-server integrations, or a frontend built in a framework where you’d rather write your own thin API client than depend on jQuery-based modules.
The full list of endpoints — dataset metadata, chart data, statistical analysis, scheduled reports, organization management, utility/connection endpoints, and the QL-Agent endpoint — along with their parameters and response shapes, is documented in Embedded Analytics Developer Guide (API).
JavaScript Development Kit (JDK)
Load one or more of the files under https://quantumlayers.com/jdk and call their exposed objects (QLAuth, QLUpload, QLDashboard, QLAnalytics, QLInsights, QLAgent, etc.) directly — they handle building the request, attaching the Bearer token, and parsing the response for you. This is the right choice for a browser-based embed where you want the same UI logic QuantumLayers itself uses, without re-implementing request plumbing.
The full list of classes, their callable functions, and the CSS-class event bindings they attach to, is documented in Embedded Analytics Developer Guide (JDK).