Embedded Analytics Developer Guide (API)

Version 0.4.4 | Last Updated: June 2026

QuantumLayers exposes its chart data, statistical analysis, and AI insights through standard WordPress AJAX endpoints secured with Bearer token authentication. This guide explains how to pull live data from QuantumLayers into any page, without rebuilding the data pipeline.

This guide assumes you already understand QL’s authentication model and have Chart.js set up. If you haven’t read it yet, start with Embedded Analytics Developer Guide for prerequisites, Chart.js setup, and a full walkthrough of all three authentication options with code examples.


Table of Contents


Authentication Endpoints

Every AJAX request below (and every endpoint in the rest of this guide) must carry a Bearer token in the Authorization header. There are three ways to obtain that token — see Authentication in the developer guide for the concepts, trade-offs, and full JWT-minting code samples. This section is the exact contract of every account and token endpoint: the POST parameters each one accepts and every JSON structure it can return.

Response envelopes

The rest of this guide uses the standard WordPress envelope — { "success": true, "data": { … } } on success and { "success": false, "data": { "message": "…" } } on failure. The account endpoints in this section are older and do not all follow it. There are three shapes, and which one you get depends on the endpoint — and, for ql_user_signup, on which error fired:

ShapeSuccessFailureUsed by
Flat{ "success": true, "message": "…", …fields }{ "success": false, "message": "…" }ql_user_signin, ql_google_login, ql_user_signout, ql_get_profile, ql_update_profile, ql_delete_account, ql_user_signup (success, and errors raised during registration)
Wrapped{ "success": true, "data": { …fields } }{ "success": false, "data": { "message": "…" } }ql_third_party_signin, ql_check_auth, ql_resend_verification, ql_list_api_tokens, ql_create_api_token, ql_revoke_api_token, and every authentication failure
Mixed{ "success": true, "data": { "message": "…" } }{ "success": false, "data": { "message": "…" } }ql_forgot_password, ql_reset_password, ql_user_signup (missing-field and terms errors)

Because endpoints that call ql_verify_auth() can fail authentication before their own code runs, a flat-shape endpoint such as ql_get_profile can still return a wrapped-shape error. The HTTP status is 200 for every response except where an endpoint below lists another status, so always branch on success rather than the status code. A client that reads both shapes:

Authentication failures

Every endpoint that requires a token (it says ql_verify_auth() under Auth) can stop before doing any work and return one of these, with HTTP 200:

Token formats and lifetimes

All three token kinds are sent the same way (see Bearer token usage). QL tells them apart by prefix:

KindFormatIssued byLifetime
Session token64 lowercase hex characters, no prefixql_user_signin, ql_google_login, ql_third_party_signin24 hours; 30 days when ql_user_signin is called with remember; always 30 days from ql_third_party_signin
API tokenqlpak_ + 48 hex charactersql_create_api_token (the API Tokens page)Until revoked, or expires_days days if set at creation
MCP OAuth access tokenqloat_ + 64 hex charactersThe MCP OAuth authorization server1 hour (refreshable); intended for MCP clients

The user object

The three sign-in endpoints return the same summary of the signed-in account in user:

Value types: values read from the database are returned as JSON strings unless the endpoint says otherwise — IDs arrive as "42", flags as "0" / "1". Parse them (Number(user.id)) rather than comparing with === against a number. The exceptions are called out per endpoint (for example, user.id is an integer in the new-account response of ql_google_login). Timestamps are "YYYY-MM-DD HH:MM:SS" in the server’s timezone, with no offset; nullable fields are null when unset. The full account record is returned by ql_get_profile.

ql_user_signup

Creates an email/password account. The account starts unverified and a verification link is emailed to it; ql_user_signin refuses the account until that link is clicked. Does not return a token.

Auth: none — public endpoint.

ParamTypeRequiredDescription
emailstringyesMust be a valid email address not already registered.
passwordstringyesAt least 8 characters.
first_namestringyes
last_namestringyes
terms_agreedstringyesMust be exactly true.
companystringno
marketing_consentstringnotrue to opt in to marketing email; anything else leaves it off.
countrystringnoFree text.
referred_by_codestringnoA referral code. Falls back to the ql_ref cookie; an unknown code is silently ignored.

Response — flat on success. Errors come in two shapes depending on where they were raised:

ql_user_signin

Email/password sign-in. Verifies the credentials, requires the account’s email to already be verified, and returns a session token. Public endpoint — callable from any origin, not just quantumlayers.com.

Auth: none — this endpoint issues the token.

ParamTypeRequiredDescription
emailstringyes
passwordstringyes
rememberanynoExtends the session from 24 hours to 30 days. Presence-based: sending the field with any value — including 0 or false — turns it on. Omit the field entirely for a 24-hour session.

Response — flat shape. Use session_token as the Bearer token on all subsequent requests.

ql_google_login

Exchanges a Google Identity Services credential (ID token) for a QL session. If an active account with the Google email exists it is signed in; otherwise a new account is created with role viewer and a verified email. Public endpoint — callable from any origin.

Side effect on existing accounts: if the matching account doesn’t already use Google sign-in (an email/password or third-party account), it is converted to Google sign-in (auth_provider becomes google, the avatar is replaced with the Google picture, and the email is marked verified). From then on ql_user_signin rejects it with Please login with Google.

Auth: none — this endpoint issues the token.

ParamTypeRequiredDescription
credentialstringyesThe ID token returned by Google Identity Services. Its audience must be QL’s Google client ID and Google must report the email as verified.
terms_agreedstringnotrue records terms acceptance. New accounts only.
marketing_consentstringnotrue to opt in. New accounts only.
countrystringnoNew accounts only.
referred_by_codestringnoAs for ql_user_signup. New accounts only.

Response — flat shape. Both success responses carry session_token; use new_user to tell them apart:

ql_third_party_signin

Exchanges an HS256 JWT signed by your backend for a QL session token. Finds or creates a shadow user keyed on provider_id + sub; email and name are updated from the JWT on every sign-in.

Auth: none — this endpoint issues the token.

ParamTypeRequiredDescription
jwtstringyesThe signed JWT (structure below).

JWT structure:

PartFieldRequiredDescription
HeaderalgyesHS256. The signature is HMAC-SHA256 with your provider’s jwt_secret.
HeaderkidyesYour registered provider_id (the id column in ql_auth_providers). The provider must be active.
PayloadsubyesStable, unique user ID in your system.
PayloademailyesThe user’s email.
PayloadexpyesExpiry as a Unix timestamp. A JWT without it is rejected as expired.
Payloadfirst_nameno
Payloadlast_nameno

Response — wrapped shape, so the token is at res.data.session_token. The session always lasts 30 days. Errors carry an HTTP status:

ql_user_signout

Signs the caller out: the session token sent with the request (as a Bearer header or session_token field) is expired immediately, and the server-side PHP session and ql_remember cookie are cleared. No parameters. Always succeeds, including when no valid token is sent.

Auth: none.

API tokens (qlpak_) and MCP OAuth tokens (qloat_) are not affected by signing out — revoke API tokens with ql_revoke_api_token. Clients should still discard the session token locally (for example with QLAuth.clearSessionToken()).

ql_check_auth

Returns a summary of the account the token belongs to. No parameters.

Auth: ql_verify_auth().

Note the key is avatar here but avatar_url in ql_get_profile. When the token is missing or invalid this endpoint returns an authentication failure (success: false), not logged_in: false — treat success: false as signed out.

ql_resend_verification

Re-sends the verification email for an unverified account. Sends at most once a minute per address (a sign-in attempt on the unverified account counts too); the response is the same whether or not a mail was sent, so it cannot be used to discover accounts.

Auth: none — public endpoint (an unverified account cannot sign in, so it has no token).

ParamTypeRequiredDescription
emailstringyesThe unverified account’s email.

ql_forgot_password

Emails a password-reset link valid for 1 hour. The link points to /ql-reset-password?key=<key>&email=<email>; pass both values to ql_reset_password. The response is identical whether or not an account exists, so it cannot be used to discover registered emails.

Auth: none — public endpoint.

ParamTypeRequiredDescription
emailstringyes

ql_reset_password

Sets a new password using the key from the reset email. The key is single-use. Does not sign the user in or return a token, and does not end existing sessions.

Auth: none — public endpoint.

ParamTypeRequiredDescription
emailstringyesThe email value from the reset link.
keystringyesThe key value from the reset link.
passwordstringyesThe new password, at least 8 characters.

ql_get_profile

Returns the full account record of the token’s user. No parameters.

Auth: ql_verify_auth().

Response — flat shape; the record is at res.user, not res.data.user:

ql_update_profile

Updates any subset of the account’s profile fields. Every parameter is optional, but at least one must be sent. All fields are validated before anything is saved, so a failed call changes nothing.

Auth: ql_verify_auth().

ParamTypeRequiredDescription
first_namestringno
last_namestringno
companystringno
countrystringno
marketing_consentstringnotrue opts in; any other value opts out.
reengagement_opt_outstringnotrue stops automatic reminder emails; any other value resumes them.
ui_modestringnodark or light.
emailstringnoA new email address. Must not belong to another account. Changing it marks the account unverified and emails a verification link to the new address; existing tokens keep working, but ql_user_signin refuses the account until the new address is verified.
passwordstringnoA new password, at least 8 characters. Only for accounts whose auth_provider is email.

Response — flat shape. There are two success responses:

ql_delete_account

Permanently deletes the account and its data. This cannot be undone.

Auth: ql_verify_auth().

ParamTypeRequiredDescription
passwordstringfor email accountsThe account’s current password.
confirm_emailstringfor google / third_party accountsThe account’s email address, retyped (case-insensitive). These accounts have no password to confirm with.

ql_list_api_tokens

Lists the caller’s API tokens, newest first. No parameters. Token values are never returned — only their metadata.

Auth: ql_verify_auth().

ql_create_api_token

Creates a long-lived API token. The raw token is returned only in this response — QL stores only its SHA-256 hash, so it cannot be shown again.

Auth: ql_verify_auth().

ParamTypeRequiredDescription
namestringyesA label for the token.
expires_daysintnoDays until expiry. Omit or send 0 for a token that never expires.

ql_revoke_api_token

Deletes one of the caller’s API tokens. Takes effect immediately.

Auth: ql_verify_auth().

ParamTypeRequiredDescription
token_idintyesThe token’s id.

Bearer token usage (session token or API token)

Every token kind is used identically — as an Authorization: Bearer <token> header on every request:

If a proxy or server configuration strips the Authorization header, send the token as a session_token POST field instead; it is only read when the header is absent.

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 — each endpoint below states which one it uses.


Analytics & Charting Endpoints

All analytics endpoints are reached via POST to https://quantumlayers.com/wp-admin/admin-ajax.php with the action field set as shown.

Dataset Metadata

ql_get_dataset_detail

Queries the ql_datasets table for the dataset record and ql_dataset_columns for its column schema. Access is checked via ql_user_can_read_dataset(), which allows access to datasets owned by the user or shared with their organisation. The column rows include pre-computed statistics (min, max, mean, stddev, distinct count, null count) calculated at upload time — these are the cheapest way to understand a dataset without loading raw rows.

Auth: ql_verify_auth() — valid Bearer token required.

POST params:

  • dataset_id (int, required)

Response:

Chart Data

ql_get_chart_data

Routes to a private method based on chart_type, then returns a complete Chart.js config object (type + data + options) ready to pass to new Chart(ctx, data). All data is loaded from the raw dataset rows up to the plan row limit. Filters are applied in PHP before aggregation. Below is what each chart type actually does internally:

  • histogram — validates the column is numeric, loads raw values, auto-computes 20 equal-width bins using Sturges-style bin width ((max−min)/20), returns a bar chart config with bin ranges as labels and frequency counts as data.
  • time_series / stacked_area — parses every value in time_column via strtotime(), sorts points chronologically, then optionally aggregates into calendar buckets (interval: day/week/month/year) using sum/avg/count/min/max. If cumulative=1, a running total is applied per series after aggregation. Gaps (null Y values) are preserved; Chart.js is instructed not to interpolate across them (spanGaps: false). Returns a line chart config; stacked_area sets fill: true on each dataset.
  • bar / horizontal_bar — validates that category_column is either a string column or a numeric column with fewer distinct values than 10% of row count (i.e. categorical). Groups all rows by category, applies the chosen aggregation (sum/avg/count/min/max) across each value_columns entry, and returns a bar config (indexAxis: 'y' for horizontal).
  • pie / doughnut — groups by category_column, aggregates value_column, sorts descending, and trims to limit categories (default 10). Returns a pie or doughnut config with auto-assigned colours.
  • scatter — loads raw x_column and y_column values as {x, y} point pairs. If color_column is provided, points are split into one dataset per distinct category value. If size_column is provided, the point radius is scaled proportionally to the column value.
  • bubble — identical to scatter but always requires size_column; the radius (r) of each bubble is normalised to a 5–30 px range across the column’s min/max.
  • box_plot / violin — groups raw y_column values by the categories in x_column. Returns data in the format expected by @sgratzl/chartjs-chart-boxplot: arrays of raw values per group, from which the library computes quartiles, whiskers, and outlier dots client-side.
  • heatmap — aggregates z_column for every unique (x_column, y_column) coordinate pair and returns a dataset in the matrix format required by chartjs-chart-matrix: an array of {x, y, v} objects.
  • regression — delegates to analytics.performLinearRegression() (OLS, supports multiple predictors). Returns a bar chart of coefficients with error bars (±1 std error), colour-coded by p-value significance.

Auth: ql_try_auth() — token optional for public datasets, required for private.

Common POST params (all chart types):

  • dataset_id (int, required)
  • chart_type (string, required) — see table below
  • filter_category_column (string, optional) — column to filter rows by exact value
  • filter_category_value (string, optional) — value to match in filter_category_column
  • filter_date_column (string, optional) — date column to range-filter
  • filter_date_from / filter_date_to (string, optional) — ISO date bounds for the date filter

Type-specific params:

chart_typeAdditional POST paramsDefaults
barcategory_column, value_columns[], aggregationaggregation: sum
horizontal_barcategory_column, value_columns[], aggregationaggregation: sum
pie / doughnutcategory_column, value_column, aggregation, limitaggregation: sum, limit: 10
scatterx_column, y_column, color_column (opt), size_column (opt)—
bubblex_column, y_column, size_column, color_column (opt)—
box_plot / violinx_column (categories), y_column (values)—
heatmapx_column, y_column, z_column, aggregationaggregation: average
time_seriestime_column, value_columns[], aggregation, interval, cumulativeaggregation: sum, interval: day, cumulative: 0
stacked_areatime_column, value_columns[], aggregation, intervalaggregation: none, interval: day
histogramcolumn_name—
regressiony_column, x_columns[]—

Response: A Chart.js config object you can pass directly to new Chart(ctx, data). On error: { success: false, data: { error: "..." } }.

ql_get_recommended_charts

Runs the full rule-based insight pipeline (same as ql_get_insights but without the AI call) and converts each insight into a chart candidate. Candidates are scored by insight_score (0–100), the top max_charts are kept, and actual Chart.js data is generated for each one via generateChartData(). Any candidate whose chart generation returns an error is silently dropped. The result is an opinionated, ranked list of charts that best reveal patterns in the data — useful for auto-populating a dashboard without any user configuration.

Auth: ql_verify_auth() — valid Bearer token required.

POST params:

  • dataset_id (int, required)
  • max_charts (int, optional, default: 20)
  • selected_columns[] (array of strings, optional) — restrict analysis to these columns
  • Filter params: same filter_category_column/value and filter_date_* as ql_get_chart_data

Response:

ql_save_chart

Recursively sanitises the chart_params array and serialises it to JSON, then inserts a row into ql_saved_charts (columns: dataset_id, user_id, chart_name, chart_type, chart_params, created_at). The chart_params value is the exact set of type-specific params you would pass to ql_get_chart_data — store the params from a chart you already rendered and you can reconstruct it later without re-running analysis.

Auth: ql_verify_auth() — valid Bearer token required.

POST params:

  • dataset_id (int, required)
  • chart_type (string, required)
  • chart_params (array, required) — the same type-specific params used in ql_get_chart_data, stored as JSON
  • chart_name (string, optional) — human-readable label

Response: { "message": "Chart saved successfully", "chart_id": 17 }

ql_get_saved_charts

Selects all rows from ql_saved_charts where dataset_id and user_id match, ordered newest-first. The chart_params field is a JSON string — parse it with JSON.parse() and pass the result directly to ql_get_chart_data to re-render the chart on demand.

Auth: ql_verify_auth() — valid Bearer token required.

POST params: dataset_id (int, required)

Response:

Note: chart_params is a JSON string. Parse it and pass to ql_get_chart_data to re-render the chart.

ql_delete_saved_chart

Fetches the ql_saved_charts row by chart_id and user_id before deleting — a chart belonging to a different user returns a 404-style error even if the chart_id exists. This ownership check is the only authorisation gate; no dataset access check is performed.

Auth: ql_verify_auth() — valid Bearer token required.

POST params: chart_id (int, required)

Response: { "message": "Chart deleted successfully" }

Statistical Analysis

ql_get_statistical_summary

Iterates every numeric column (inferred_type of integer or float) whose distinct_count exceeds 10% of the dataset’s row count — this threshold filters out effectively categorical numerics like status codes or boolean flags. For each qualifying column, min/max/mean/std are read directly from the pre-computed ql_dataset_columns row (no raw-data load needed). The raw column values are then loaded once to compute median, Q1, Q3, skewness (third standardised moment), and kurtosis (fourth standardised moment minus 3 — i.e. excess kurtosis).

Auth: ql_verify_auth() — valid Bearer token required.

POST params: dataset_id (int, required)

Response: array of per-column objects:

ql_get_correlation_matrix

Selects all continuous numeric columns (same 10%-distinct threshold as the statistical summary), loads their raw values, then computes the full Pearson r matrix in PHP. The result is cached as a WordPress transient for one hour — the cache key includes the dataset ID, so a second call within the hour returns instantly without reloading data. Requires at least two qualifying columns; returns an error otherwise. The cache is per-dataset (not per-user), so all users sharing a dataset see the same cached matrix.

Auth: ql_verify_auth() — valid Bearer token required.

POST params: dataset_id (int, required)

Response:

matrix[i][j] is the Pearson r between columns[i] and columns[j]. Values range from −1 to 1.

ql_get_distribution_analysis

Loads all non-null numeric values for the requested column, sorts them, and computes: mean, variance, std, range, IQR, full percentile set (p5/Q1/median/Q3/p95), skewness (third standardised moment), and excess kurtosis. Outliers are detected using the 1.5×IQR fence rule: values below Q1 − 1.5×IQR or above Q3 + 1.5×IQR are flagged. Up to 50 outlier values are returned in the response. Normality is classified as approximately normal (|skewness| < 0.5 and |kurtosis| < 0.5), non-normal (either exceeds 1), or moderately skewed otherwise. The column must be integer or float; string and date columns return an error.

Auth: ql_verify_auth() — valid Bearer token required.

POST params: dataset_id (int, required), column_name (string, required)

Response:

ql_get_pca_analysis

Selects continuous numeric columns (10%-distinct threshold), loads raw values, z-score standardises each column (subtract mean, divide by std), then builds the covariance matrix. Eigenvalues and eigenvectors are computed using the Power Iteration method implemented in PHP. Components are sorted by eigenvalue descending. The transformed data (PC scores) is capped at the first 1,000 rows in the response to limit payload size. Results are cached for one hour keyed by dataset_id + n_components. If n_components is less than 2 or omitted, all components are returned. Requires at least 2 qualifying columns and 10 rows.

Auth: ql_verify_auth() — valid Bearer token required.

POST params:

  • dataset_id (int, required)
  • n_components (int, optional) — number of components to return; if omitted or less than 2, defaults to the total number of eligible columns

Response:

ql_get_anova_analysis

For every pair of (categorical column × continuous numeric column), runs a one-way ANOVA: groups numeric values by category label, computes Sum of Squares Between (SSB) and Within (SSW), derives the F-statistic (MSB/MSW), and approximates the p-value using an incomplete beta function (no external statistics library). Additionally computes Cohen’s d between the two groups with the largest pairwise mean difference, as a standardised effect-size measure. eta_squared = SSB / (SSB + SSW) gives the proportion of variance explained by the grouping. The significance label is derived from the p-value: not_significant (p ≥ 0.05), moderately_significant (p < 0.05), highly_significant (p < 0.01), or insufficient_data (fewer than 3 rows or fewer than 2 groups).

Auth: ql_verify_auth() — valid Bearer token required.

POST params: dataset_id (int, required)

Response:

AI Insights

ql_get_insights

Two-stage pipeline. Stage 1 — rule-based insights (always runs): nine parallel analyses each produce typed insight objects, which are merged into one list:

  • Correlation — flags strong Pearson r relationships (|r| > threshold) between numeric columns.
  • ANOVA — flags categorical columns that significantly explain variance in a numeric column (F-test p < 0.05).
  • Distribution — flags high skewness, excess kurtosis, and columns with a high outlier rate.
  • Temporal — detects monotonic trends and seasonality in date/time columns.
  • Regression — runs OLS on numeric column pairs and surfaces strong predictive relationships.
  • Categorical — measures distribution entropy per categorical column; flags highly imbalanced or highly uniform distributions.
  • Categorical relationships — runs chi-square tests between pairs of categorical columns to detect non-independence.
  • Cross-correlation — detects lagged relationships between time series; first runs an ADF stationarity test and differences non-stationary series before computing cross-correlation.
  • Multicollinearity (VIF) — computes Variance Inflation Factor for each numeric column; flags predictors with VIF above threshold as redundant.

All insights that carry a p-value in their metadata are then subjected to Benjamini-Hochberg FDR correction at α = 0.05, removing insights that become non-significant after accounting for multiple comparisons. The surviving insights are sorted by importance descending and truncated to max_insights.

Stage 2 — AI holistic analysis (runs only if an Anthropic or OpenAI key is configured and the user’s monthly token budget is not exhausted): compiles a dataset context object (column stats, top rule-based insights, top 5 chart candidates) and sends it to ql_call_llm() with a 4,000-token budget and the system prompt “You are an expert data analyst who provides clear, actionable insights for business users.” If a user_prompt is supplied, it is appended to steer the analysis. The AI response is prepended to the insight array as a holistic_analysis type object with importance: 100. If the token budget is exceeded, a warning insight is prepended instead and the AI call is skipped.

Auth: ql_verify_auth() — valid Bearer token required.

POST params:

  • dataset_id (int, required)
  • selected_columns[] (array, optional) — restrict insights to these columns
  • max_insights (int, optional) — capped at the plan limit; defaults to plan maximum
  • user_prompt (string, optional) — freeform question or instruction passed to the AI for the holistic analysis
  • include_ai (bool, optional) — defaults to true. Set to false to skip Stage 2 (AI holistic analysis) entirely and return only the rule-based statistical insights, saving tokens.
  • Filter params: filter_category_column, filter_category_value, filter_date_column, filter_date_from, filter_date_to

Response:

Note: The first element is the AI holistic insight (if AI is configured and the token budget is not exhausted). Remaining elements are rule-based insights sorted by importance descending.


Dataset Management Endpoints

These endpoints create, update, sync, and delete datasets. Every source type (CSV upload, SQL, REST API, SFTP, Google Sheets, and merged) has its own set of handlers. All write operations use ql_verify_auth() and check dataset ownership via ql_user_can_write_dataset() before acting. The visibility parameter is accepted by all create/update handlers and must be one of private, public, org, or group.

Listing Datasets

ql_get_dashboard_data

Returns all datasets owned by the authenticated user, ordered newest-first. This is the natural starting point for any integration: call it once to discover which dataset IDs you want to pass to the analytics, charting, and insight endpoints.

Auth: ql_verify_auth() — valid Bearer token required. POST params: none.

Response:


CSV Upload

ql_upload_dataset

Creates a new dataset from a CSV file upload. The file is validated against an allowlist of MIME types (text/csv, text/plain, application/csv, application/vnd.ms-excel) using finfo — not just the file extension. File size is checked against the plan limit before the file is moved. The file is saved to wp-uploads/quantumlayers/<dataset_id>_<timestamp>.csv and its SHA-256 checksum stored in ql_dataset_files. The dataset row is created with status = 'processing' and then ql_process_csv_file() is called synchronously: it reads the CSV header to detect column names, samples rows to infer types (integer/float/string/date/datetime), and computes per-column statistics (min, max, mean, stddev, distinct count, null count) stored in ql_dataset_columns. The status is set to ready on success.

Auth: ql_verify_auth(). Request type: multipart/form-data.

  • csv_file (file, required) — the CSV file
  • dataset_name (string, required)
  • visibility (string, optional, default: private)

Response: { "message": "Upload successful!", "dataset_id": 42, "dataset_name": "sales_2024" }

ql_update_upload_dataset

Updates metadata and/or replaces the CSV data for an existing source_type = 'upload' dataset. Rejects datasets of other source types with an explicit error. If a new csv_file is included it is processed via the shared ql_process_uploaded_csv() helper, which replaces the stored file, re-runs column analysis, and updates ql_dataset_columns and row_count. Metadata changes (dataset_name, visibility) and CSV replacement can be combined in a single call — the response message describes which operations occurred.

Auth: ql_verify_auth() + ownership check. Request type: multipart/form-data.

  • dataset_id (int, required)
  • dataset_name (string, optional)
  • visibility (string, optional)
  • csv_file (file, optional) — if provided, replaces the dataset’s data

Response: { "message": "...", "row_count": 15000 } (row_count present only when a CSV was processed).

ql_resync_upload_dataset

Replaces a CSV upload dataset’s data with a newly uploaded file, without changing any metadata. Equivalent to ql_update_upload_dataset with only a csv_file and no metadata fields. A convenience endpoint for sync-only workflows.

Auth: ql_verify_auth() + ownership check. Request type: multipart/form-data.

  • dataset_id (int, required)
  • csv_file (file, required)

Response: { "success": true, "message": "...", "row_count": 15000 }


Excel Upload

Excel files are uploaded in two steps. First ql_inspect_excel_file stores the workbook in a temporary area and returns its sheets, the detected header row, and a preview, along with a token that identifies the stored file. You can call ql_excel_sheet_preview to preview a different sheet or header row. Then ql_upload_excel_dataset (new dataset) or ql_update_upload_excel_dataset (existing upload dataset) converts the chosen sheet to CSV and processes it exactly like a CSV upload. Accepted formats: .xlsx, .xlsm, .xls. The token is tied to the user who uploaded the file, is consumed by a successful create/update, and unused temporary files are deleted after 24 hours.

ql_inspect_excel_file

Auth: ql_verify_auth(). Request type: multipart/form-data.

ParamTypeRequiredDescription
excel_filefileyesThe workbook. Subject to your plan’s maximum file size.

Response — an inspection object describing the workbook:

Errors (data.message): No file uploaded, Upload error: <PHP upload error code>, Please upload a valid Excel file (.xlsx, .xlsm, or .xls), a file-size limit message, Could not read the Excel file. It may be corrupted or password-protected., No worksheets were found in this file., Excel support is not available on this server. Please contact support.

ql_excel_sheet_preview

Re-inspects the already-uploaded file for another sheet or header row. Nothing is re-uploaded.

Auth: ql_verify_auth().

ParamTypeRequiredDescription
tokenstringyesFrom ql_inspect_excel_file.
sheet_namestringnoSheet to preview. Omit for the first sheet.
header_rowintno1-based header row. Omit or send 0 to auto-detect.

Response — the same inspection object as ql_inspect_excel_file, including the same token. Fails with Uploaded Excel file has expired. Please re-upload it. if the token is unknown, belongs to another user, or has already been consumed.

ql_upload_excel_dataset

Creates a new source_type = 'upload' dataset from an inspected workbook. The chosen sheet becomes the dataset’s CSV; the source workbook is deleted afterwards. Counts against your plan’s dataset limit, row limit, and file-size limit.

Auth: ql_verify_auth().

ParamTypeRequiredDescription
tokenstringyesFrom ql_inspect_excel_file.
dataset_namestringyes
sheet_namestringnoSheet to import. Omit, or send a name that doesn’t exist, to use the first sheet.
header_rowintno1-based header row. Omit or 0 to auto-detect.
visibilitystringnoprivate (default), public, org, or group.

If processing fails the new dataset row is removed and data.message explains why (token expired, dataset/row/size limit reached, unreadable worksheet, Failed to secure file).

ql_update_upload_excel_dataset

Renames an existing upload dataset, changes its visibility, and/or replaces its data from a newly inspected workbook. Only works on datasets you own whose source_type is upload. At least one change is required.

Auth: ql_verify_auth() + ownership check.

ParamTypeRequiredDescription
dataset_idintyes
dataset_namestringnoNew name.
visibilitystringnoAs for ql_upload_excel_dataset. Legacy alternative: is_private (0 = public, anything else = private).
tokenstringnoFrom ql_inspect_excel_file. When present, the dataset’s data is replaced by the chosen sheet.
sheet_namestringnoOnly used with token.
header_rowintnoOnly used with token.

Errors (data.message): Dataset ID required, Dataset not found or access denied, This dataset is not an uploaded file. Use the appropriate update method for connected datasets., No data to update, Failed to update dataset, and any Excel processing error listed above.

SQL Database

ql_connect_sql_database

Creates a live SQL-backed dataset. Supported db_type values are mysql and postgresql. The connection is tested via ql_test_database_connection() before any database rows are written — a failed test returns an error immediately. On success: a ql_datasets row is created with source_type = 'db', the password is encrypted with ql_encrypt_credential() and stored in ql_sql_connections, then ql_sync_sql_connection() executes sql_query against the remote database, streams the result set into QL’s internal storage, and runs full column analysis. The default port is 3306 for MySQL and 5432 for PostgreSQL if omitted.

Auth: ql_verify_auth().

  • dataset_name (string, required)
  • db_type (string, required) — mysql or postgresql
  • host (string, required)
  • port (int, optional) — defaults to 3306 / 5432
  • database (string, required) — database/schema name
  • username (string, required)
  • password (string, required)
  • sql_query (string, required) — the SELECT query whose result set becomes the dataset
  • visibility (string, optional)

Response: { "message": "Database connected successfully", "dataset_id": 42, "connection_id": 7, "rows_fetched": 5000 }. If the initial sync fails but the connection was stored, a "warning": true flag is added and rows_fetched is omitted.

ql_update_sql_connection

Updates credentials, query, or metadata for an existing SQL connection. Identified by connection_id. Any field can be omitted to leave it unchanged; if a new password is supplied it is re-encrypted. Does not re-sync data — call ql_resync_connection after updating to pull fresh data.

Auth: ql_verify_auth() + ownership check on connection_id.

  • connection_id (int, required)
  • dataset_name, db_type, host, port, database_name, username, password, sql_query, visibility (all optional)

Response: { "message": "Connection updated successfully" }


ql_test_sql_connection

Checks that QuantumLayers can connect to a database with the given credentials, without saving anything. Use it to validate a form before calling ql_connect_sql_database.

Auth: ql_verify_auth().

ParamTypeRequiredDescription
db_typestringyesmysql, postgresql, redshift, sqlserver, synapse, clickhouse, or bigquery.
hoststringyes**Not required for bigquery.
portintnoOmit for the engine’s default port.
databasestringyesDatabase name; for bigquery, the project ID.
usernamestringno
passwordstringno

ql_generate_sql_query

Writes a SELECT query from a plain-language description. QuantumLayers connects with the given credentials, reads the live schema, and asks the AI model for a query in that database’s dialect. Nothing is executed or saved; review the SQL, then pass it as sql_query to ql_connect_sql_database. Uses AI tokens from your plan’s monthly budget.

Auth: ql_verify_auth().

ParamTypeRequiredDescription
intentstringyesWhat the query should return, in plain language.
db_typestringnomysql, postgresql, redshift, sqlserver, synapse, clickhouse, or bigquery. Default mysql.
hoststringyes**Not required for bigquery.
portintnoDefault port for the engine when omitted.
databasestringyes
usernamestringno
passwordstringno

If the model’s reply isn’t the expected JSON, sql contains the raw reply and explanation is empty, so check the SQL before using it.

REST API

ql_connect_api

Creates a dataset backed by a REST API endpoint. The URL is validated with a scheme check (http:// or https://) rather than FILTER_VALIDATE_URL to support URLs with colons in path segments (e.g. GA4’s /properties/123:runReport). A live connection test is performed before any rows are written. The API key is encrypted with ql_encrypt_credential(). Supports OAuth2: if a refresh_token is provided, QL stores it and automatically refreshes the access token when it is within 5 minutes of expiry at sync time. Custom request_headers are stored as a JSON string and merged with the default Authorization and Accept: application/json headers on every sync. response_path is a dot-notation path into the JSON response used to locate the array of records (e.g. data.items).

Auth: ql_verify_auth().

  • dataset_name (string, required)
  • api_url (string, required)
  • http_method (string, optional) — GET (default) or POST
  • api_key (string, optional) — sent as Authorization: Bearer <key>
  • request_headers (JSON string or array, optional) — extra headers merged at sync time
  • request_body (string, optional) — raw body for POST requests; sets Content-Type: application/json
  • response_path (string, optional) — dot-notation path to the records array in the JSON response
  • refresh_token (string, optional) — OAuth2 refresh token
  • oauth_provider_name (string, optional)
  • oauth_scope (string, optional)
  • visibility (string, optional)

Response: { "message": "API connected successfully", "dataset_id": 42, "connection_id": 9, "rows_fetched": 1200 }

ql_update_api_connection

Updates credentials, headers, URL, or metadata for an existing API connection identified by connection_id. Any field can be omitted to leave it unchanged. Does not re-sync — call ql_resync_connection afterwards.

Auth: ql_verify_auth() + ownership check. POST params: connection_id (required) + any subset of the fields accepted by ql_connect_api.


SFTP

ql_create_sftp_connection

Creates a dataset backed by a file on an SFTP server. Supports password auth or private key auth — at least one must be supplied. Both credentials are encrypted with ql_encrypt_credential() before storage. A live connection test (ql_sftp_test_connection()) is performed first; if it fails no rows are written. On success, ql_sftp_sync_dataset() downloads the file at remote_path/filename, processes it as a CSV, and runs full column analysis. The dataset is created with source_type = 'sftp'.

Auth: ql_verify_auth().

  • dataset_name (string, required)
  • host (string, required)
  • port (int, optional, default: 22)
  • username (string, required)
  • password (string, optional) — required if private_key not provided
  • private_key (string, optional) — PEM private key; required if password not provided
  • remote_path (string, required) — directory path on the SFTP server
  • filename (string, required) — filename within remote_path
  • visibility (string, optional)

Response: { "dataset_id": 42, "connection_id": 3, "rows_fetched": 8000 }

ql_update_sftp_connection

Updates host, credentials, remote path, filename, or metadata for an existing SFTP connection. Any field can be omitted. Does not re-sync — call ql_sync_sftp_dataset after updating.

Auth: ql_verify_auth() + ownership check. POST params: connection_id (required) + any subset of ql_create_sftp_connection fields.

ql_sync_sftp_dataset

Triggers an immediate re-download and re-import of the file from the SFTP server for an existing SFTP connection. Decrypts stored credentials, opens the SSH connection, downloads the file, replaces the stored CSV data, and re-runs column analysis.

Auth: ql_verify_auth() + ownership check. POST params: connection_id (int, required).

Response: { "message": "Dataset synced successfully", "row_count": 8500 }

ql_delete_sftp_connection

Deletes an SFTP connection record. Pass delete_dataset: true to also drop the associated dataset and its data table.

Auth: ql_verify_auth() + ownership check. POST params: connection_id (int, required), delete_dataset (bool, optional).

Response: { "message": "Connection deleted successfully" }


ql_test_sftp_connection

Connects to an SFTP server and checks that the target file exists and is readable, without saving anything. filename may contain * wildcards, in which case the most recently modified match is reported — the same file ql_create_sftp_connection would import.

Auth: ql_verify_auth().

ParamTypeRequiredDescription
hoststringyes
portintnoDefault 22.
usernamestringyes
passwordstringone ofPassword auth.
private_keystringone ofPEM private key, for key auth.
remote_pathstringyesDirectory containing the file.
filenamestringyesFile name, or a pattern such as export_*.csv.

Response — note that the result object, including its own success and message, is the whole data payload on both success and failure:

file_size is in bytes; file_modified is in the server’s timezone.

Google Sheets

ql_connect_google_sheet

Creates a dataset backed by a Google Sheet. Requires an OAuth2 access_token obtained via the Google Picker flow (scope: drive.file). Because drive.file tokens are rejected by the Sheets API, QL uses the Drive API export endpoint (/export?mimeType=text/csv) to download the sheet as CSV. A connection test is performed before writing any rows. The access token is stored in plaintext in ql_google_sheet_connections and a 1-hour expiry is calculated; at sync time, if the token is within 1 hour of expiry, ql_refresh_google_token() is called automatically using the stored refresh_token. If range_notation is omitted, the entire sheet is imported.

Auth: ql_verify_auth().

  • spreadsheet_id (string, required) — Google Sheets spreadsheet ID
  • access_token (string, required) — current OAuth2 access token
  • connection_name (string, optional, default: "Google Sheet Connection") — becomes the dataset name
  • sheet_name (string, optional, default: "Sheet1")
  • range_notation (string, optional) — A1 notation range, e.g. A1:F500
  • refresh_token (string, optional)
  • visibility (string, optional)

Response: { "message": "Google Sheets connected successfully", "dataset_id": 42, "connection_id": 5 }

ql_update_google_sheet_connection

Updates the spreadsheet ID, sheet name, range, tokens, or dataset metadata for an existing Google Sheet connection. Any field can be omitted. Does not re-sync — call ql_resync_connection after updating.

Auth: ql_verify_auth() + ownership check. POST params: connection_id (required) + any subset of ql_connect_google_sheet fields.


ql_sync_google_sheet

Re-downloads a Google Sheet connection’s data now, refreshing the access token first if it is close to expiry. Equivalent to ql_resync_connection for Google Sheets, but addressed by connection_id.

Auth: ql_verify_auth() + ownership check.

ParamTypeRequiredDescription
connection_idintyesThe Google Sheet connection’s ID.

Kaggle

Connect directly to a public or private Kaggle dataset using your Kaggle credentials. QuantumLayers calls the Kaggle API v1 download endpoint, receives a ZIP archive, extracts the first CSV file it finds, and processes it the same way as a regular upload — running column type inference and computing per-column statistics. The connection is stored internally as an API connection, so ql_update_api_connection can be used to update credentials or metadata, and ql_resync_connection (or the dedicated sync below) triggers a fresh download.

ql_connect_kaggle

Creates a new dataset by downloading a Kaggle dataset. kaggle_owner and kaggle_dataset are the two slug segments in the Kaggle dataset URL — e.g. for kaggle.com/datasets/acmecorp/sales-data the owner is acmecorp and the dataset is sales-data. The API key is your Kaggle account API token (found under Account → API in your Kaggle profile).

Auth: ql_verify_auth().

  • dataset_name (string, required) — name for the dataset in QuantumLayers
  • kaggle_username (string, required) — your Kaggle account username
  • kaggle_api_key (string, required) — your Kaggle API key
  • kaggle_owner (string, required) — the dataset owner slug from the Kaggle URL
  • kaggle_dataset (string, required) — the dataset slug from the Kaggle URL
  • visibility (string, optional, default: private)

Response: { "message": "Kaggle dataset connected successfully", "dataset_id": 42, "connection_id": 11, "rows": 8000 }

ql_sync_kaggle_dataset

Re-downloads the Kaggle dataset ZIP using the stored credentials, extracts the first CSV, replaces the stored data, and re-runs column analysis. Use this to pull an updated version of the Kaggle dataset on demand.

Auth: ql_verify_auth() + ownership check. POST params: dataset_id (int, required).

Response: { "success": true, "row_count": 8500 }

ql_update_kaggle_dataset

Updates the Kaggle connection metadata (name, owner/dataset slugs, visibility, optionally new credentials) and immediately re-syncs the dataset. Omit kaggle_username and kaggle_api_key to keep the existing stored credentials.

Auth: ql_verify_auth() + ownership check. POST params: dataset_id (required), dataset_name (required), kaggle_owner (required), kaggle_dataset (required), kaggle_username (optional), kaggle_api_key (optional), visibility (optional).

Response: { "success": true, "row_count": 8500 }


Merged Datasets

ql_create_merged_dataset

Joins two or more existing datasets on shared columns and materialises the result as a new dataset with source_type = 'merged'. Requires at least 2 datasets. The first dataset in the array is the base; each subsequent entry specifies a join column (which must exist in that dataset) and a join type. Joins are chained left-to-right: dataset 2 joins onto dataset 1, dataset 3 joins onto the result of that join, and so on. Join records are stored in ql_dataset_joins. After the join records are created, ql_build_merged_columns() reads the column schemas of all source datasets and writes the merged column schema. The actual row data is assembled at query time from the source datasets — no copy of the rows is stored. Always created as visibility = 'private'.

Auth: ql_verify_auth().

Response: { "success": true, "dataset_id": 55 }

ql_update_merged_dataset

Replaces the name and entire join configuration of an existing merged dataset. All existing ql_dataset_joins rows for the dataset are deleted and re-created from the new datasets array using the same chaining logic as ql_create_merged_dataset. The underlying source data is not touched.

Auth: ql_verify_auth() + ownership check.

  • dataset_id (int, required) — must be a source_type = 'merged' dataset
  • name (string, required)
  • datasets (array, required) — same structure as ql_create_merged_dataset

Response: { "message": "Merged dataset updated successfully" }


ql_sync_merged_dataset

Rebuilds a merged dataset’s metadata from the current state of its source datasets: the column list, types, and statistics are re-derived, and the row count is recomputed by running the join. Call it after a source dataset has been re-synced. The join configuration is unchanged.

Auth: ql_verify_auth() + ownership check (you must own the merged dataset).

ParamTypeRequiredDescription
dataset_idintyesA dataset whose source_type is merged.

Common Operations

ql_resync_connection

Re-fetches data from the remote source for any connected dataset (source_type of db, api, or google_sheet). Looks up the connection record for the dataset’s source type, then delegates to the appropriate sync function: ql_sync_sql_connection(), ql_sync_api_connection(), or ql_sync_google_sheet_connection(). For Google Sheets, optional fresh access_token / refresh_token values can be passed in the same request to update tokens before syncing.

Auth: ql_verify_auth() + ownership check.

  • dataset_id (int, required)
  • access_token (string, optional) — Google Sheets only
  • refresh_token (string, optional) — Google Sheets only

Response: { "message": "Data synced successfully", "row_count": 5200 }

ql_delete_dataset

Permanently deletes a dataset and all associated data. Access is checked via ql_user_can_write_dataset(). Deletion cascades through every related table in order: physical CSV files are deleted from wp-uploads/quantumlayers/ first, then rows are removed from ql_dataset_files, ql_dataset_columns, ql_dataset_joins, ql_google_sheet_connections, ql_api_connections, ql_sql_connections, ql_sftp_connections, and finally ql_datasets. Saved charts and insights for the dataset are also removed. This operation is irreversible.

Auth: ql_verify_auth() + ql_user_can_write_dataset().

  • dataset_id (int, required)

Response: { "message": "Dataset deleted successfully" }

ql_set_dataset_visibility

Changes the visibility of any dataset. Also registered as ql_toggle_dataset_privacy for backward compatibility. Accepts the modern visibility string parameter or the legacy is_public boolean (0 = private, 1 = public); if both are sent, visibility takes precedence. Access is checked via ql_user_can_write_dataset().

Auth: ql_verify_auth() + ql_user_can_write_dataset().

  • dataset_id (int, required)
  • visibility (string) — private | public | org | group
  • is_public (int, legacy) — 0 = private, 1 = public; ignored if visibility is present

Response: { "message": "Visibility updated to public" }


Scheduled Reports Endpoints

Scheduled reports automatically generate PDF or HTML summaries of your datasets and email them to recipients on a recurring schedule. All endpoints require authentication.

List Reports

Action: ql_get_scheduled_reports
Params: none

Response: { "reports": [{ "id", "report_name", "frequency", "schedule_time", "timezone", "recipients", "format", "is_active", "next_run", "last_run" }], "count": N }

Create Report

Action: ql_create_scheduled_report

ParamTypeDescription
report_namestringDisplay name for the report
frequencystringdaily, weekly, monthly, or custom
schedule_timestringTime in HH:MM:SS format (UTC unless timezone set)
timezonestringIANA timezone, e.g. America/New_York
schedule_day_of_weekint0–6 (Sun–Sat); used when frequency is weekly
schedule_day_of_monthint1–31; used when frequency is monthly
recipientsstringComma-separated email addresses
formatstringpdf, html, or both
datasetsJSONArray of dataset config objects (see below)

Each object in the datasets array supports these fields:

FieldTypeDescription
dataset_idintRequired. Dataset to include
include_insightsboolGenerate AI insights for this dataset
include_suggested_chartsboolAuto-generate chart suggestions
suggested_chart_countintNumber of charts to suggest (default 3)
saved_chart_idsarrayIDs of saved charts to include
insight_columnsarrayColumns to focus AI insights on
insight_filter_columnstringColumn name to filter data before insights
insight_filter_valuestringValue to match in the filter column

Update Report

Action: ql_update_scheduled_report
Send report_id plus any subset of the fields from Create Report that you want to change.

Delete Report

Action: ql_delete_scheduled_report
Params: report_id

Get Report Details

Action: ql_get_report_details
Params: report_id

Run Report Now

Action: ql_run_report_now
Params: report_id
Triggers an immediate out-of-schedule execution and emails the recipients.

Toggle Report Status

Action: ql_toggle_report_status
Params: report_id, is_active (1 to enable, 0 to pause)


Monitor Endpoints

A monitor re-runs the insight detectors on one or more datasets on a schedule, compares the findings with the previous run, and notifies you only when a finding appears or disappears (for example, a correlation that becomes significant, or a trend that ends). Notifications go by email, signed webhook, and/or Slack — see Monitor Webhook & Slack Delivery below for those fields and the webhook payload.

All endpoints use the wrapped envelope ({ success, data }, HTTP 200) and require ql_verify_auth(). Endpoints that take a monitor_id return Monitor not found or access denied when the monitor doesn’t exist or belongs to someone else. As elsewhere, database values come back as strings unless noted.

The monitor object

Returned by ql_get_monitors and ql_get_monitor_details. Times are in the server’s timezone except next_run, which is UTC; schedule_time is interpreted in the monitor’s timezone.

ql_get_monitors adds three computed fields to each monitor: dataset_count (number of watched datasets), last_successful_run (time of the latest successful run, or null) and last_run_had_changes ("1" if that run found changes, "0" if not, null if there has been no successful run).

Dataset configuration

ql_create_monitor and ql_update_monitor take datasets as a JSON-encoded array. Each element configures one watched dataset; the array order becomes display_order.

FieldTypeRequiredDescription
dataset_idintyesA dataset you can read.
section_titlestringnoHeading for this dataset in notifications. Defaults to the dataset name.
watched_columnsstring[]noOnly report findings that involve these columns. Omit or send an empty array for all columns.
insight_filter_columnstringnoWith insight_filter_value: analyse only rows where this column equals that value.
insight_filter_valuestringno
insight_filter_date_columnstringnoWith insight_filter_date_from / insight_filter_date_to: analyse only rows whose date falls in the range.
insight_filter_date_fromstringnoStart date, e.g. 2026-01-01.
insight_filter_date_tostringnoEnd date.
max_findingsintnoMaximum findings tracked per run for this dataset. Minimum 1, default 30.

ql_create_monitor

Creates an active monitor and schedules its first run. Requires a Pro subscription; without one the call fails with HTTP 403 and Monitors require a Pro subscription. Upgrade to unlock statistical monitoring.

ParamTypeRequiredDescription
monitor_namestringyes
datasetsJSON stringyesArray of dataset configurations; at least one.
frequencystringnohourly, daily, weekly (default), monthly, or custom. custom is currently scheduled like daily. Runs are picked up by a job that checks every 15 minutes, so they can start up to 15 minutes late.
schedule_timestringnoHH:MM:SS or HH:MM, default 09:00:00. For hourly, only the minutes and seconds are used.
timezonestringnoIANA timezone, default America/Montreal. An invalid value is treated as UTC.
schedule_day_of_weekintno0–6 (0 = Sunday), for weekly. Out-of-range values are clamped.
schedule_day_of_monthintno1–31, for monthly; clamped to the month’s last day when needed.
recipientsstringwhen email is onComma-separated email addresses.
formatstringnoEmail format: html (default), pdf, or both. Other values become html.
Delivery fieldsnodelivery_methods, webhook_url, webhook_secret, webhook_finding_types, webhook_cooldown_minutes (0–10080), slack_webhook_url — see Monitor Webhook & Slack Delivery.

ql_update_monitor

Changes any subset of a monitor’s settings. Send monitor_id plus only the fields to change; omitted fields keep their values. Accepts every ql_create_monitor field. Changing any schedule field recalculates next_run. Sending datasets replaces the whole dataset list and must contain at least one dataset. Sending an empty string for schedule_day_of_week or schedule_day_of_month clears it. The Pro check is not repeated.

All fields are validated before anything is saved, so a rejected call leaves the monitor unchanged.

ql_get_monitors

Lists your monitors, newest first. No parameters.

ql_get_monitor_details

Returns one monitor with its datasets, its 10 most recent runs, and the findings that are currently active.

ParamTypeRequiredDescription
monitor_idintyes

ql_run_monitor_now

Runs the monitor immediately and waits for it to finish, including sending notifications for any changes. It does not change the schedule. Can take a while on large datasets, so allow a generous client timeout.

ParamTypeRequiredDescription
monitor_idintyes

ql_toggle_monitor_status

Pauses or resumes a monitor. A paused monitor keeps its settings and findings but is not run on schedule.

ParamTypeRequiredDescription
monitor_idintyes
is_activeintyes1 to resume, 0 to pause. Any other value, or leaving it out, is rejected.

ql_delete_monitor

Permanently deletes a monitor together with its dataset list, run history, and findings.

ParamTypeRequiredDescription
monitor_idintyes

ql_reveal_monitor_secret

Returns the decrypted value of a monitor’s webhook signing secret or Slack webhook URL. These are never included in the monitor object, so this is the only way to read them back.

ParamTypeRequiredDescription
monitor_idintyes
fieldstringyeswebhook_secret or slack_webhook_url. Anything else fails with Invalid field.

ql_rotate_monitor_webhook_secret

Replaces the webhook signing secret with a newly generated one and returns it. The old secret stops verifying signatures immediately, so update your receiver at the same time.

ParamTypeRequiredDescription
monitor_idintyes

ql_get_monitor_delivery_log

Returns webhook and Slack delivery state for a monitor: the 50 most recent deliveries (one per event and channel, including retries still pending) and the 100 most recent individual attempts.

ParamTypeRequiredDescription
monitor_idintyes

Monitor Webhook & Slack Delivery

Monitors (statistical change detectors — see ql_create_monitor / ql_update_monitor) can deliver findings by email/PDF, an outbound webhook, and/or a Slack Incoming Webhook, in any combination. QuantumLayers only detects and notifies — it sends a signed event describing exactly what changed; deciding what to do about it, and doing it, is entirely on your side.

Enabling Email / Webhook / Slack Delivery

Pass these fields to ql_create_monitor or ql_update_monitor alongside the usual monitor fields. All three channels require a Pro subscription (the same entitlement that already gates Monitors and Scheduled Reports), and all three are optional — enable any combination, but at least one must stay enabled. recipients is only required while email is included in delivery_methods.

FieldTypeDescription
delivery_methodsarraySubset of ["email","webhook","slack"]. Any combination; defaults to ["email"] when omitted entirely. Rejected if it would leave every channel disabled.
webhook_urlstringHTTPS endpoint to receive the signed monitor.finding event. Required if webhook is enabled. Rejected if it isn’t https:// or resolves to a private/loopback/link-local/reserved address.
webhook_secretstringHMAC signing secret. Omit to auto-generate a high-entropy one the first time Webhook delivery is enabled. View or rotate it later via ql_reveal_monitor_secret / ql_rotate_monitor_webhook_secret — it is never included in ql_get_monitor_details‘s response.
webhook_finding_typesarrayOptional filter — only these insight types (e.g. correlation, trend, outliers) fire the webhook. Omit for all types the monitor tracks.
webhook_cooldown_minutesintDebounce window per finding + direction before re-firing (default 60). A finding that disappears and later reappears is always treated as a new event, regardless of this window.
slack_webhook_urlstringA Slack Incoming Webhook URL (see setup steps below). Required if slack is enabled. Masked the same way as webhook_secret on read.

Only findings that already passed the monitor’s own statistical significance threshold (the same detector output the email alert uses) are ever considered — there is no separate, looser threshold for Webhook/Slack.

Webhook Payload

Each finding is delivered as an HTTP POST with this JSON body — the whole finding, not prose:

links.finding_url is always present — open it to verify the finding in the dashboard before taking any irreversible action, or hand it to an agent that can pull richer context through the QuantumLayers MCP server.

Headers

HeaderDescription
X-QL-Signaturesha256=<hex> — HMAC-SHA256 of the raw request body using your webhook_secret.
X-QL-TimestampUnix seconds the request was sent. Reject any request with a timestamp more than 5 minutes from your clock — this bounds replay of a captured request.
X-QL-NonceA UUID unique to this delivery attempt, for additional replay protection if you keep a short-lived cache of seen nonces.
X-QL-Event-IdMirrors event_id in the body — stays constant across retries, so dedupe on this for idempotent processing.
X-QL-Delivery-IdMirrors delivery_id — unique per attempt.

Verifying the Signature (PHP)

Reliability

  • Delivery is asynchronous — a monitor run never blocks on your endpoint’s response time.
  • Up to 5 attempts with exponential backoff (30s, 60s, 120s, 240s, capped at 15 minutes) on a non-2xx response or connection failure.
  • 10-second connect/read timeout per attempt.
  • Redirects are followed (up to 3 hops) with the same HTTPS/SSRF validation re-applied to each hop.
  • Every attempt (timestamp, HTTP status, attempt number, truncated response) is logged and visible in the monitor’s Delivery Log panel — fetch it via ql_get_monitor_delivery_log.

Slack Setup

  • In Slack, go to your workspace → Settings & administration → Manage apps (or api.slack.com/apps) and create an app, or open an existing one.
  • Enable Incoming Webhooks, then Add New Webhook to Workspace and pick the channel that should receive alerts.
  • Copy the generated https://hooks.slack.com/services/... URL and pass it as slack_webhook_url when creating or updating the monitor, with slack included in delivery_methods.

Each finding posts as a Block Kit message: a header (finding type + dataset), the same plain-language summary as the email, a fields row (effect size, significance score, direction, sample size), a timestamp context line, and a “View in QuantumLayers” button linking to finding_url. A future release adds an Approve action alongside it (a signed link, not implemented yet) — the actions block already reserves room for it, so existing Slack integrations won’t need to change when it ships.


Organization Management Endpoints

Organizations allow multiple users to share datasets and collaborate under a single subscription. Seats are billed through Paddle; adding or removing members automatically adjusts the seat quantity. All endpoints require authentication; admin-level operations additionally require that the caller holds an admin or manager role within the org.

List My Organizations

Action: ql_get_my_organizations
Params: none

Create Organization

Action: ql_create_organization

ParamTypeDescription
namestringOrganization display name
license_typestringstartup or enterprise
max_usersintMaximum number of seats
billing_emailstringEmail used for Paddle billing

Get Organization

Action: ql_get_organization
Params: organization_id

Update Organization

Action: ql_update_organization
Send organization_id plus any subset of name, license_type, max_users, billing_email.

Delete Organization

Action: ql_delete_organization
Params: organization_id

Member Management

List members — ql_get_organization_members

Add member — ql_add_organization_member
Role must be one of member, admin, or manager. The seat count is synced to Paddle automatically.

Remove member — ql_remove_organization_member

Update role — ql_update_member_role

Billing History

Action: ql_get_organization_billing
Params: organization_id, limit (optional, default 12)


Utility & Connection Endpoints

These endpoints cover auth introspection, user profile, subscription limits, connection metadata, merged-dataset internals, and template lookups. All require a valid Bearer token unless noted otherwise.

Check Auth

Action: ql_check_auth — documented with its full request and response structure under Authentication Endpoints → ql_check_auth. Note that it returns success: false (not logged_in: false) when the token is missing or invalid.

Get Profile

Action: ql_get_profile — documented with its full response structure under Authentication Endpoints → ql_get_profile, alongside ql_update_profile and ql_delete_account.

Subscription Status

Action: ql_get_subscription_status
Params: none
Useful for gating features in your embedded UI before calling endpoints that enforce subscription limits.

Connection Endpoints

List all connections — ql_get_user_connections (no params)
Returns all four connection types in a single call.

Get a specific connection — each getter accepts connection_id or dataset_id (one is required). Credentials are stripped from the response.

ActionReturns
ql_get_sql_connection{ connection } — SQL metadata (no password)
ql_get_api_connection{ connection } — API URL, method, auth type, headers (JSON-decoded)
ql_get_sftp_connection{ connection } — SFTP host/port/path (no password or private key)
ql_get_google_sheet_connection{ connection } — spreadsheet ID, sheet name (no credentials JSON)

Get connection by dataset ID (any type) — ql_get_connection_details
Inspects the dataset’s source_type and returns the matching connection object with a type field (sql, api, sftp, google_sheets, kaggle, or upload).

Merged Dataset Helpers

These complement the ql_create_merged_dataset and ql_update_merged_dataset endpoints already documented in the Dataset Management section.

List non-merged datasets available for joining — ql_get_available_datasets (no params)
Only returns datasets with status = ready and source_type != merged.

Get columns of a dataset (for join key selection) — ql_get_dataset_columns_for_merge
Params: dataset_id

Get a merged dataset with its join config — ql_get_merged_dataset
Params: dataset_id

Get join config only (lightweight) — ql_get_merge_config
Params: dataset_id — same as above but skips the parent dataset row. Returns an error if the dataset is not of source_type = merged.

Delete a merged dataset — ql_delete_merged_dataset
Params: dataset_id

Templates

Pre-built connection templates stored in the plugin database. Both endpoints accept an optional service filter.

SQL query templates — ql_get_query_templates
Params: service (optional, e.g. MySQL — omit or pass Other for all)

REST API templates — ql_get_api_templates
Params: service (optional, e.g. Stripe — omit or pass Custom for all)

OAuth Helpers

These support the OAuth flows used when connecting Google Sheets and OAuth-protected REST APIs.

ql_get_oauth_providers

Lists the names of the OAuth providers configured on this install, for use as oauth_provider_name in ql_connect_api. Credentials are never returned. No parameters.

Auth: ql_verify_auth().

ql_get_oauth_provider_config

Returns the public part of one provider’s configuration, which is enough to send the user to the provider’s authorization page. The client secret is never returned.

Auth: ql_verify_auth().

ParamTypeRequiredDescription
provider_namestringyesA name from ql_get_oauth_providers.

ql_google_exchange_code

Exchanges a Google authorization code from the Google Identity Services popup flow for access and refresh tokens, using QuantumLayers’ Google client credentials. Use the tokens for ql_connect_google_sheet.

Request format differs from other endpoints: put the action in the URL (admin-ajax.php?action=ql_google_exchange_code) and send a JSON body with Content-Type: application/json. Form fields are ignored.

Auth: ql_verify_auth(). Because the body is JSON, send the token in the Authorization: Bearer header; the session_token form-field fallback doesn’t apply.

Response — Google’s token response, passed through unchanged as data:

ql_google_refresh_token

Gets a new Google access token from a refresh token. Same request format and authentication as ql_google_exchange_code: action in the URL, JSON body, and the token in the Authorization header.

Response — Google’s response in data: access_token, expires_in, scope, token_type (no new refresh token). Errors match ql_google_exchange_code, with Refresh token is required (HTTP 400) and Token refresh failed: … (HTTP 500).

Internal Endpoints

These actions also exist but aren’t part of the integration API. Payment-provider webhooks are called by the provider and verify its signature: ql_paddle_webhook and ql_stripe_webhook. Admin tools only work for WordPress administrators: ql_admin_delete_orphan_files, ql_admin_run_csv_migration, ql_generate_license_signing_key, ql_generate_on_premise_license, and ql_download_on_premise_package.


QL-Agent Endpoint

The QL-Agent is a multi-turn conversational AI that can autonomously list datasets, generate charts, run statistical analysis, create and modify scheduled reports, and more. You can embed the agent loop directly in your own UI by calling ql_run_agent for each user turn.

ql_run_agent

Runs one turn of the agentic loop. The agent receives the user’s natural-language message plus the prior conversation history, calls Claude with a set of QL tool definitions (list datasets, generate charts, get insights, manage reports, etc.), executes any tool calls autonomously, and returns both the updated message history and a final plain-text reply. The loop runs up to 10 iterations per call. Requires an Anthropic API key to be configured in QuantumLayers settings.

Auth: ql_verify_auth() — valid Bearer token required. Token budget is checked against the user’s plan before each iteration.

POST params:

  • message (string, required) — the user’s natural-language request for this turn
  • history (JSON string, optional) — the messages array returned by the previous call; pass [] or omit for the first turn

Response:

Multi-turn usage:


Complete Code Example

End-to-end: authenticate via a third-party JWT, fetch a bar chart for dataset 42, and render it with Chart.js.


Chart Type Quick Reference

Pass one of the following values as chart_type to ql_get_chart_data.

chart_typeRequired paramsChart.js type rendered
barx_column, y_column, aggregationbar
horizontal_barx_column, y_column, aggregationbar (indexAxis: ‘y’)
linex_column, y_column, aggregationline
scatterx_column, y_columnscatter
piecategory_column, value_column, aggregationpie
doughnutcategory_column, value_column, aggregationdoughnut
bubblex_column, y_column, size_columnbubble
box_plotcategory_column, value_columnboxplot (chartjs-chart-boxplot)
heatmapx_column, y_column, value_columnmatrix (chartjs-chart-matrix)
time_seriesdate_column, value_column, aggregationline (time scale)
histogramcolumn_name, optional binsbar (computed bins)
areax_column, y_column, aggregationline (fill: true)