Update History (2026-05-11)
This is the Update History for API version 2026-05-11: one living document per version, new entries on top. Each entry is tagged with the api_update date that the plugin will report in its /guide response. Read this top-to-bottom to see how the API has grown since the initial release of this version, and match the most recent entry against your server's api_update to know which features are actually available to you.
The entries below the Initial Release of 2026-05-11 section are carried over from the predecessor version 2025-10-20 for context. The breaking-change entry at the top describes the contract delta you need to know when migrating.
2026-05-11 — api_update: 2026-05-11 — Version 2026-05-11 initial release — symmetric calc column declaration
This is a breaking-change version bump. Read this entry before migrating from 2025-10-20.
Added:
- ✅ Symmetric
calccolumn declaration (T87c). Amaterial.columnreference inside anycalcexpression is now the single declaration site for that column on both thefromside and thejoin.withside. The executor fetches and preserves the column from whichever side the material lives on, then drops it from the output (onlykeepcolumns +calckeys are returned). This unifies a long-standing asymmetry wherefrom-side columns were auto-fetched butjoin-side columns were not, causing aggregates likeCOUNT(click_event.pv_id)to silently return 0 whenclick_eventwas the join target. Since: 2026-05-11. Reported in/guideasfeatures_detail.calc_join_symmetric.enabled: true. - ✅
E_CALC_COLUMN_UNRESOLVED(validator). Pre-execution validation now rejects anycalcexpression whosematerial.columnreference is not in scope (the material is not infromorjoin.with) or does not exist in the material's schema. This converts what used to be a silent zero-row result into a clear validation error, restoring the "AI repair loop" signal for AI-composed queries. See Errors. - ✅ JOIN
on.left/on.rightpinpoint scope pre-validation + structured error details (T87, originally shipped 2026-05-10 on2025-10-20).on.leftmust equal the resolved physical material name of thefromside (with view chains pre-resolved);on.rightmust equal thejoin.withstring verbatim. Errors now carryside("left"or"right"),received_value,expected_prefix, and ahintfield insidedetails. Carried forward into2026-05-11unchanged.
Changed (breaking from 2025-10-20):
- 🔄
calcsemantics:material.columnis the column declaration. In2025-10-20, the only declaration site that consistently triggered fetch + preserve was thefromside. From2026-05-11, the rule is symmetric and the spec is unified: the appearance ofmaterial.columninside acalcexpression is itself the declaration that the executor fetches that column, regardless of which side of the join it lives on. The output still contains onlykeepcolumns pluscalckeys —calc-input columns are preserved through merge but stripped from the final result, preservingGROUP BY = keepsemantics.
Migration from 2025-10-20:
- Queries that worked are still correct:
from-side calc references behave identically. No client change needed. - Queries that previously silently returned 0 rows because they referenced a join-side column inside
calcwill now return correct aggregates without modification. Re-run them. - Queries that pre-emptively added a join-side column to
keepsolely to makecalc"see" it should remove that column fromkeep. Keeping it inkeepis still legal but changes the GROUP BY grain and is no longer required. - Invalid
calcreferences (typo in column name, material absent fromfrom/join.with) that used to silently produce 0 rows now fail at validation withE_CALC_COLUMN_UNRESOLVED. Fix the reference or remove it. - The
features.calc_join_symmetricflag in/guidetells you whether the server is running the new symmetric rule. Treat its absence as "old behavior — re-check column placement before relying on join-side calc."
Known limitation (carried forward, intentionally out of scope for this bump):
- 🚧 View chain × calc symmetry is not in scope. When
from[0]references a previously defined view (view chaining), the validator skipsE_CALC_COLUMN_UNRESOLVEDfor that view, and the executor falls back to the legacy Material-runtime path for calc preservation. Real material-name prefixes still resolve through the Material runtime; bare view-name prefixes in calc (<from_view>.column) are not yet preserved. This is tracked as a future task and is intentionally not blocking this version bump.
Why this is a version bump and not an update:
- The fix changes observable execution semantics for queries that had been considered valid by the validator. Even though the new semantics are arguably "what every client always meant," anything depending on the old behavior (e.g., an integration that worked around the silent-zero bug by adding columns to
keep) is now strictly speaking running on a different contract. Per the versioning policy, that is a version bump, not an update.
Pre-2026-05-11 history (carried over from 2025-10-20 for context)
2026-04-29 — api_update: 2026-04-29 — materials.supports_all flag
Added:
- ✅
materials.{name}.supports_all: true | falseinmaterials.yaml— every material now declares whether it can be queried withtracking_id: "all"(the cross-site aggregate built nightly). Currentlytrueforallpv,click_event,datalayer_event,events_template;falseforgsc,goal_x,page_version,ga4_*. AI clients and admin UIs should read this flag before offering "all sites" as a tracking_id choice for a given material. - ✅
features.materials_supports_allreported in/guide— clients can detect availability of the new flag by checking this feature flag. Older servers may omitsupports_allfrom individual materials; treat its absence as "unknown — try and handle errors." Since: 2026-04-29 - ✅ Synced
ai/materials.yamlandai/qal-validation.yamlwith the qa-labo source for both this update and the prior 2026-04-17 update — the AI-served YAML now reflects theprev_page_id/next_page_id/prev_url/prev_title/next_url/next_titlecolumns and theallpv_prev_next_pagefeature flag that were added on 2026-04-17. The human-readablematerials/allpv.mdand the/guidereference example were already correct; only the AI-facing YAML lagged behind.
Why this update is still non-breaking:
- The
supports_allflag is purely additive metadata. Existing queries — including ones that passtracking_id: "all"to materials that turn out to besupports_all: false— behave exactly as before (still return their pre-existing error / empty result). - The qa-labo runtime continues to enforce
tracking_idsemantics; the flag only documents the existing behavior so AI clients can decide before sending the query.
2026-04-17 — api_update: 2026-04-17 — allpv page transition columns
Added:
- ✅
allpv.prev_page_id/allpv.next_page_id— two new physical columns (uint32) recording the page viewed immediately before and after each page view within the same session. Landing PVs haveprev_page_id = 0; exit PVs havenext_page_id = 0. Filter on these values to extract landing or exit page views in a single QAL query. - ✅
allpv.prev_url/allpv.prev_title/allpv.next_url/allpv.next_title— four virtual columns resolved fromprev_page_id/next_page_idvia theqa_pagesmaster table. Usekeepto include human-readable URLs and titles for the previous/next pages. - ✅
features.allpv_prev_next_pagereported in/guide— clients can detect availability of the new columns by checking this feature flag. Since: 2026-04-17
Why this update is still non-breaking:
- All new columns are additive. Existing queries that do not reference
prev_page_id/next_page_idare completely unaffected. - The new physical columns use
nullable: true, default: 0, so date ranges predating this update return0(no transition data) rather than errors.
2026-04-14 — api_update: 2026-04-14 — Developer Manual restructure + features_detail + since
Added:
- ✅
/guidenow returnsfeatures_detail— a richer map keyed by feature name, with per-entry{ enabled, since }shape. The existing flatfeaturesmap is preserved as a projection so older clients keep working. New AI clients should preferfeatures_detailand compare each feature'ssinceagainst the server'sapi_updateto decide availability. - ✅ Per-feature
sinceinqal-validation.yaml— every entry underfeatures:in the validation manifest now carries asince: YYYY-MM-DDtag whenenabled: true. This is the authoritative source the/guideendpoint projects from. - ✅ Top-level
version/updateinmaterials-manifest.yaml— and optionalsince:tags on individual materials and fields. Purely additive; existing consumers are unaffected. - ✅ New
ai/subdirectory underdeveloper-manual/api/2026-05-11/containing the concise AI instruction set (README.md) and the two machine-readable YAML specs (materials.yaml,qal-validation.yaml). The/guideendpoint now serves this subdirectory instead of the legacy flat markdown files, so MCP / LLM clients receive exactly the content they need — no human-oriented prose.
Changed:
- 🔄 Developer manual reorganized into
concepts/,materials/,reference/, andai/subdirectories. The top-level entry point is now framed as "Get Started with AI" rather than a conventional curl-first Quick Start. The legacy flat files (qal.md,qal-validation.md,materials.md,endpoints.md) have been removed in favor of this structure. - 🔄 Each material now has its own page with a hand-crafted sample table so the grain of the data is visible at a glance. Sample tables show only three ID columns (
page_id,session_id,pv_id) — the full ID set and schema live inai/materials.yaml. - 🔄
/guidedocumentation content is now AI-optimized. The endpoint previously fetched four markdown files aimed at humans (index.md,endpoints.md,materials.md,qal.md); it now fetches one conciseREADME.mdplus the two YAML specs. Human-oriented pages remain on docs.qazero.com for people who want to read them.
Why this update is still non-breaking:
- The legacy flat
featuresmap is preserved unchanged in the/guideresponse. - Existing client code that reads
version,api_update,features,sites, or the presence ofdocumentation.sectionscontinues to work. The content ofdocumentation.sectionshas changed, but clients that treat it as opaque pass-through (as AI clients do) are unaffected. - No QAL query shape has changed. No existing material or column has been removed or renamed.
2026-04-13 — api_update: 2026-04-13 — Documentation v1.2.0
Added:
- ✅ QAL
make.sort— view-level row ordering and top-N. Placesort: { by, order, top }inside a view inmaketo sort the view's output afterfilter/join/keep/calc.byaccepts both qualified (allpv.url) and unqualified (pageviews) names,orderisasc/desc,topis an optional row cap. See the What is QAL? concept page and the authoritativeqal-validation.yaml. - ✅
/guidefeatures.sortnow reportstrueon servers running this update — clients should use it to detect availability instead of hard-coding.
Clarified:
- 🔧 Sorting is intentionally view-level, not result-level. There is no
result.sort. A single QAL query may ask for multiple chained views, and each view owns its own ordering. - 🔧
resultwhitelist today is exactlyuse/limit/count_only.result.sample/result.include_count/result.returnare not currently accepted (previously documented as "validator-accepted no-ops"; that description is now obsolete).
2026-04-11 — api_update: 2026-04-11 — Documentation v1.1.2
Runtime:
- ✅
/guidenow returns anapi_updatefield and afeaturesmap keyed byfilter,join,calc,view_chaining,sort,sample,include_count,return_file,return_csv,return_parquet. This is the authoritative runtime state — clients (including AI agents) should consultfeaturesrather than hard-coding feature availability. - ✅ Plugin constants
QAHM_API_VERSIONandQAHM_API_UPDATEare introduced and used to populate the guide response.
Corrected (documentation only, carried forward from v1.1.1):
- 🔧
result.sortis rejected asE_RESULT_FORBIDDEN_KEY, andresult.sample/result.include_countpass validation but are no-ops. Examples, feature table, and validation manifest were updated accordingly in the previous revision and remain authoritative.
2026-04-10 — api_update: 2026-04-10 — Documentation v1.1.0
Added:
- ✅ QAL
filter(flat form,eq/neq/gt/gte/lt/lte/in/contains/prefix/between) - ✅ QAL
join(single equi-join per view, id-column only) - ✅ QAL
calcaggregation with whitelisted functions (COUNT,COUNTUNIQUE,SUM,AVERAGE,MIN,MAX) - ✅ View chaining —
frommay reference previously defined views in the samemakeblock - ✅ M:N join filter requirement (
E_JOIN_FILTER_REQUIRED) documented - ✅ New materials:
goal_1..goal_N,ga4_age_gender,ga4_country,ga4_region,page_version,click_event,datalayer_event,events.{name} - ✅
allpvbehavioral columns (depth_position,deep_read,stop_max_sec,stop_max_pos,exit_pos,is_submit,dead_click_image_count,irritation_click_count,scroll_back_count,content_skip_count,exploration_count) - ✅
allpvpage-type booleans (is_article,is_product,is_form, ...) and virtual goal columns (is_goal_1..is_goal_10) - ✅
gsccolumn-DB schema (clicks,impressions,position_x100) with virtualctr,position,position_weighted
Changed:
- 🔄 QAL Validation Manifest aligned with current runtime rules (filter/join/calc features enabled)
- 🔄 Materials Reference restructured to catalogue all materials from
/guide
These features reflect work merged into qa-labo through April 2026. See the Developer Blog for context on what was built and why.
2025-10-06 - Documentation v1.0.0
Added:
- ✅
tracking_idrequired field in all QAL queries - ✅
/guideendpoint now returns comprehensive server information - ✅
plugin_versionfield in/guideresponse - ✅ Site-specific materials and goals in
/guideresponse - ✅ System limits information
- ✅ Compatibility matrix documentation
Removed:
- ❌
/materialsendpoint (replaced by/guide) - ❌ Goal-related fields from
allpvmaterial - ❌ Several deprecated fields:
session_id,tracking_domain,path_prefix,utm_content,utm_term,version_id
Changed:
- 🔄
country→country_code(ISO 3166-1 alpha-2) - 🔄 QAL version specification moved from query body to URL parameter
Plugin Version:
- 🔌 Requires QA Platform Plugin 3.0.0.0+
2025-10-20 - Initial Release
Released:
- Basic QAL with
fromandkeepoperations - Two materials:
allpvandgsc - Simple result options:
limitandcount_only