Skip to content

What changed since 0.15.16

For integrators on the 0.15.16-beta reference

One page covering every client-visible change from 0.15.16-beta to 0.23.0-beta, with a checklist at the end. Per-version notes are in the Changelog.

SFF Confidential

You agree to keep all Data strictly confidential and protect it with the same level of care you use to protect your own confidential information, but no less than reasonable care. Access to the Data is limited to your employees, contractors or agents who are informed of and agree to these confidentiality obligations. You are not permitted to disclose, distribute, publish, or otherwise share the Data, in any manner or medium, with any third party, without the prior written consent of the Foundation. You may not use the Data for commercial purposes.


AOI API — What Changed, 0.15.16-beta → 0.23.0-beta

For: integrators working from the 0.15.16-beta external REST reference (2026-08-18)
Base URL: https://aonav.ai · Date: 2026-10-26
Full reference: REST_API_REFERENCE_EXTERNAL.md (0.23.0-beta)

⚠️ Beta API — SUBJECT TO CHANGE. Fields marked SUBJECT TO CHANGE may be renamed or reshaped before 1.0. Clients should ignore unknown response keys.

Availability. Everything below through 0.22.3-beta is live on https://aonav.ai. The 0.23.0-beta items (AI impact scores, cluster descriptions, skills summary, and the 10.23 cluster refresh) go live with the next production release; we will confirm the date. GET /api/version reports which version you are talking to.


1. Act on these (breaking or behaviour-changing)

# Since Change What to do
B1 0.17.0 Wages: 11 rows per MSA size, not 3. Rows are keyed on years_in (0–10). Read years_in (or filter ?years_in=); never index wage rows by position. experience_level "0"/"5"/"10" still return the same rows as before.
B2 0.17.0, 0.23.0 Cluster set refreshed. 0.17.0 withdrew 82 clusters; 0.23.0 withdraws 8 and restores 23 of the 82. Treat 404 / not-found on a previously valid cluster_id or O*NET code as normal; don't cache cluster ids indefinitely.
B3 0.16.7 GET /api/occupations/cluster:{id}/pathways → 410 Gone. (It answered 500 before; it never returned data.) Use GET /api/clusters/{id}/cluster-destinations and /skill-adj-clusters. The O*NET form /api/occupations/{onet}/pathways is unchanged.
B4 0.16.1 TITLECONVERT returns distinct clusters on the no-company path (was distinct O*NET codes). limit now counts clusters. If you relied on seeing sibling O*NET codes, use group=onet.
B5 0.16.2 match_source can be compound, e.g. name_corpus+catalog_exact. Test with membership ("catalog" in s), not equality.
B6 0.16.0 MCP: POST /mcp/v1 removed (404). Use POST /mcp. Same tools/call body. Always send a Bearer token; tool errors now come back as HTTP 200 with the error in the JSON-RPC body (was 400).
B7 0.16.10 MCP: single-wrapped result. content[0].text parses to the DSL envelope in one json.loads (was two); also available as result.structuredContent. The format argument is retired. Parse once, or unwrap while the object still has a content key (works with both shapes). Sending format is harmless — it is ignored.
B8 0.23.0 premium_skill_description_1/2/3 are always null. The provider replaced per-skill descriptions with one paragraph. Show premium_skills_summary instead. The keys stay in the response.
B9 0.23.0 Premium skills blank for ~300 more clusters. The 10.23 delivery publishes skills for 479 of 794 clusters (was ~765). Handle premium_skill_N: null and premium_skills_summary: null.

2. New response fields (additive)

2.1 AI impact scores — ai_impact_scores (0.23.0, SUBJECT TO CHANGE)

Three continuous scores per cluster from BGI's 2026-10-23 research: demand_score (−5…+5), entry_level_score (−5…+5), skills_change_score (0…5).

Where: GET /api/clusters/{id}/ai-impact and /api/occupations/cluster:{id}/ai-impact (top-level key) · GET /api/occupations/{onet}/ai-impact (inside data, plus occupation_scores) · GET /api/clusters/{id} (inside data).

Coverage: 169 of 794 clusters. For the rest:

"ai_impact_scores": {"available": false, "message": "AI impact information not available at this time."}

When scores exist (GET /api/clusters/94/ai-impact, abridged):

"ai_impact_scores": {
  "available": true,
  "demand_score": -3.8,
  "entry_level_score": -0.2,
  "skills_change_score": 1.8,
  "method": "Weighted avg by monthly postings",
  "methodology_version": "2026-10-23",
  "occupations_with_data": [
    {"onet_code": "19-1029.02", "title": "Molecular and Cellular Biologists",
     "monthly_postings": 1397.5, "demand_score": -5.0, "entry_level_score": 0.5, "skills_change_score": 1.1}
  ],
  "occupations_without_data": [
    {"onet_code": "19-1029.00", "title": "Biological Scientists, All Other"},
    {"onet_code": "19-1029.04", "title": "Biologists"}
  ],
  "notes": ["These scores were computed from 3 of 5 occupations in this cluster and do not describe those in occupations_without_data. ..."],
  "scales": {"demand_score": "-5 to +5. ...", "entry_level_score": "-5 to +5. ...", "skills_change_score": "0 to 5. ..."},
  "note": "Scores are continuous and published to one decimal. Report them as published; ..."
}

Rules for display:

  • available: false → show the message (or omit the section). Don't estimate from neighbouring clusters.
  • A null score in an available block was not published (14 clusters have no entry-level score, 1 has no skills-change score). Don't fill it.
  • Block itself null → our read failed. Treat as an error and retry, not as "no data".
  • Quote scores to one decimal. BGI publishes no bands or labels; don't add "high/low".
  • If occupations_without_data is non-empty, the score doesn't describe those occupations — read notes.
  • Occupation form: occupation_scores is that occupation's own row, or null (with a note) when the cluster score wasn't computed from it.

2.2 Cluster text (0.23.0)

Field Where Notes
cluster_description GET /api/clusters/{id} What the work is, one or two sentences. All 794 clusters
premium_skills_summary GET /api/clusters/{id}, both AI-impact endpoints, /api/occupations/{onet}/pathways One paragraph covering all the cluster's premium skills; null when it has none

2.3 Wage growth — wage_growth (0.17.5)

"wage_growth": {"p50_growth_0_10": 0.150226, "tier": "medium", "basis": "...", "note": "..."}

On /api/occupations/{onet}/wages (beside wages_by_segment), /api/clusters/{id}/wages (beside data) and GET /api/clusters/{id} (inside data). null when unpublished.

p50_growth_0_10 is employment-weighted across metro sizes; the wage-row percentiles are not. Computing growth from the rows gives a different number — that's expected. Quote one and say which. tier is relative to one delivery, not an absolute band.

2.4 TITLECONVERT display label (0.17.7)

Every /api/titleconvert row gains display_name and display_source (cluster_name | onet_title | bgi_suboccupation) — the name that most directly matches what was typed. Render display_name || cluster_name || onet_title.

Typed cluster_name display_name
creative director Art Directors Creative Director
civil Infrastructure Engineers Civil Engineers

2.5 Adjacency data_status (0.16.8)

Every row and the baseline on /skill-adj-clusters and /cluster-destinations carry data_status: attributed or taxonomy_only. A taxonomy_only baseline (e.g. Chief Executives, military occupations) now returns 200 with data: [] and a data_status_note instead of an "Unknown cluster_id" error.

2.6 Other

  • ?years_in=7 filter on the wage endpoints (0.17.0).
  • premium_skill_details (skill + description) on /pathways and /ai-impact (0.17.0; descriptions null since 0.23.0).
  • query_info.hint_applied on /api/titleconvert when a curated mapping answered the title (0.15.18); match_source is then hint.

3. Behaviour changes with no shape change

Since Change
0.16.1 Boolean query params (_fast, clarify, _catalog, ...) accept 1/true/yes/on; previously only true worked and ?x=1 read as false.
0.16.2 TITLECONVERT confidence reports high/low from source agreement on the catalog path. Use it, not match_score, to decide whether to confirm with the user.
0.16.2 – 0.18.6 TITLECONVERT accuracy and speed work: larger name catalog, provider suboccupation names as first-class aliases, typeahead ranked by usage. Same request/response shape; top results may differ from what you saw before.
0.16.11 / 0.17.0 Adjacency neighbours are a curated, complete set — not a sample or top-N. Destinations widen from 3 to 5. Iterate premium_skill_N / skill_adj_cluster_N keys rather than hardcoding three; read count/total.
0.17.0 ai_flag deprecated — still served and filterable; no longer updated. Prefer ai_impact_scores.
0.17.0 /api/clusters/{id}/retention and /api/companies/{name}/hiring now have a stable sort order.
0.22.3 COMPANYCONVERT matches one-word typos it used to miss (deloite → Deloitte).

4. Dataset counts (0.23.0-beta)

0.15.16 doc Now
Companies ~1,750 ~1,750
O*NET occupations 1,016 948
Clusters with attributes 561* 794
Company × occupation-cluster pairs ~55,000 ~38,500
Clusters with AI impact scores — 169
Clusters with an ai_flag 177 176

* The 0.15.16 doc's figure; it predates the 9.22 and 10.23 refreshes.


5. Client checklist

  • [ ] Wage code reads years_in, expects 11 rows per MSA size (B1)
  • [ ] Not-found on an old cluster_id / O*NET code is handled (B2)
  • [ ] No calls to /api/occupations/cluster:{id}/pathways (B3)
  • [ ] match_source checked by membership (B5)
  • [ ] MCP clients on POST /mcp, parsing the result once (B6, B7)
  • [ ] UI shows premium_skills_summary, not per-skill descriptions (B8)
  • [ ] Nulls handled for premium skills and the summary (B9)
  • [ ] TITLECONVERT UI renders display_name (2.4)
  • [ ] AI impact section handles available: false, null scores, and a null block differently (2.1)

Questions: tech@schultzfamilyfoundation.org