DSL Reference (recommended)¶
Primary interface
DSL is the recommended, forward-looking way to use AOI. It is a superset
of the legacy REST API: anything REST can do, DSL can do, plus
batch title resolution, the SUBMIT SAMPLE / SUBMIT TITLECONVERT channels,
and admin writes. New capabilities land on DSL first.
Beta - subject to change
Commands, modifiers, and response shapes may change during the beta. Features marked Experimental are especially likely to change. Breaking changes are announced in the Changelog.
Calling the DSL endpoint¶
Submit a single command as a JSON string to POST /api/dsl with a Bearer token.
TOKEN=$(curl -s -X POST https://aonav.ai/auth/login \
-H 'Content-Type: application/json' \
-d '{"username":"<user>","password":"<pass>"}' | jq -r '.tokens.access_token')
curl -s -X POST https://aonav.ai/api/dsl \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"query": "TITLECONVERT \"registered nurse\" EXPAND CLUSTER"}'
DSL requires the dsl:execute permission (analyst role or above). Admin write
commands additionally require the admin role.
Syntax rules¶
- Case-insensitive keywords; string values must be quoted (
"..."), numbers are unquoted. - No pipe operators.
|and;are not supported - run multi-step workflows as sequential queries. - Clause order:
<VERB> <SUBJECT> [FOR ...] [WHERE ...] [modifiers]. Modifiers terminate one another (WHERE/EXPAND/ORDER BY/GROUP BY/LIMIT/OFFSET/RANGE/WITH SCORE/USING). Rankedis a badge sentinel meaning any non-NA badge (Platinum or Gold); it may be written bare or quoted.
Response envelope¶
Errors are structured to guide recovery:
Verbs¶
| Verb | Purpose |
|---|---|
LIST |
Query many rows |
GET |
Fetch a single entity or its related data |
COUNT |
Count matching rows (supports GROUP BY) |
SEARCH |
Full-text search (companies) |
TITLECONVERT |
Resolve a job title to O*NET occupation(s) |
COMPANYCONVERT |
Resolve a company name / ticker / alias |
CLUSTERCONVERT |
Resolve a title to clusters (legacy) |
SUBMIT SAMPLE |
Submit a client learning sample |
SUBMIT TITLECONVERT |
Record which TITLECONVERT result the user picked |
ANALYZE BADGES / VERSION / HELP |
Utility |
UPDATE / ADD / REMOVE |
Admin writes (admin role) |
Operators¶
IS IS NOT / NOT = == IN (...) CONTAINS
> < >= / GTE <= / LTE BETWEEN
IS NULL IS NOT NULL
Logical: AND OR ( )
Sentinel: IS "Ranked"
Modifiers¶
LIMIT n(default 100, max 1000) -OFFSET n-RANGE start-endORDER BY <field[,field]> [ASC|DESC]GROUP BY ONET | <badge dimension>EXPAND <token[,token]>-INCLUDE OCCUPATIONS-WITH SCOREFORMAT JSON | MERMAID(adjacency subjects only - see below)USING <flags>(experimental matcher toggles - see below)CLARIFY(TITLECONVERT)
ORDER BY fields depend on the subject:
- Companies:
PLATINUMGOLDBADGE_COUNTALPHAALPHA_DESCINDUSTRYRELEVANCECOMPANY_COUNT - Occupations:
TITLEONET_CODECLUSTER_IDCLUSTER_NAMEAI_FLAGENTRY_BARRIER_TRENDEXPERTISE_PREMIUM_TRENDJOB_LEVELCOMPANY_COUNT_* - Clusters:
CLUSTER_NAMECLUSTER_IDAI_FLAG<trend>OCCUPATION_COUNTBA_PLUS_SHARE - Industries:
INDUSTRYCOMPANY_COUNT - Companies for a cluster:
ALPHAENTRY_POSTING_COUNTTOTAL_POSTING_COUNTBADGEINDUSTRYWYWM(WYWM_EARLY_CAREER|WYWM_GROWTH|WYWM_STABILITY)
EXPAND tokens:
- Companies:
RELATIONSHIPS,TIMESTAMPS - Clusters / occupations:
AI_IMPACT,COMPANY_HIRING - Occupations for a company:
TIMESTAMPS - TITLECONVERT:
CLUSTER/CLUSTERS/CLUSTER_DETAILS/DETAILS(synonym:RESOLVE CLUSTER [DETAILS]) - Adjacency:
NAMES,OCCUPATIONS,COMPANY_HIRING
FORMAT values (0.16.9-beta):
JSON— the default envelope. ExplicitFORMAT JSONis a no-op.MERMAID— adds amermaiddiagram string plusmermaid_notealongside the rows, onLIST SKILL_ADJACENT_CLUSTERSandLIST DESTINATION_CLUSTERSonly. Node ids are cluster ids, so any node can be fed back intoFOR CLUSTER "<id>". Pair withEXPAND NAMESto get labelled nodes.
Any other value is a -32602 carrying valid_values; so is FORMAT MERMAID on
a subject that has no edges to draw. Before 0.16.9-beta the clause was parsed as
nothing and dropped in silence.
LIST¶
Subjects: COMPANIES INDUSTRIES CLUSTERS OCCUPATIONS COMPANY_CLUSTERS
OCCUPATION_CLUSTERS EARLY_CAREER_COMPANIES SKILL_ADJACENT_CLUSTERS
DESTINATION_CLUSTERS COMPANY_BADGES OCCUPATION_BADGES.
LIST COMPANIES WHERE INDUSTRY IS "Healthcare" AND OVERALL_BADGE IS "Gold"
LIST COMPANIES WHERE INDUSTRY_ID IS 352526
LIST COMPANIES WHERE COMPANY_UID IN ("AN85","OL1194")
LIST COMPANIES WHERE BADGE_GROWTH IS Ranked ORDER BY PLATINUM LIMIT 20
LIST COMPANIES ORDER BY PLATINUM WITH SCORE
LIST COMPANIES FOR CLUSTER "187" ORDER BY TOTAL_POSTING_COUNT DESC LIMIT 15
LIST COMPANIES FOR CLUSTER "187" ORDER BY WYWM LIMIT 15
LIST COMPANIES FOR OCCUPATION WHERE ONET_CODE IS "15-1252.00" LIMIT 20
LIST OCCUPATIONS WHERE SEARCH IS "software" WHERE CLUSTER_ID IS 187
LIST OCCUPATIONS WHERE AI_FLAG IS "Raising the Bar" EXPAND AI_IMPACT LIMIT 10
LIST OCCUPATIONS FOR COMPANY "Amazon" WHERE TOP10 IS "1" EXPAND TIMESTAMPS
LIST INDUSTRIES WHERE SEARCH IS "tech" ORDER BY COMPANY_COUNT DESC LIMIT 10
LIST CLUSTERS WHERE SEARCH IS "analytics" WHERE PREMIUM_SKILL CONTAINS "Cloud"
LIST COMPANY_CLUSTERS WHERE COMPANY IS "Microsoft"
LIST OCCUPATION_CLUSTERS WHERE ONET_CODE IS "15-1252.00"
LIST COMPANY_BADGES WHERE INDUSTRY IS "Healthcare" AND BADGE_GROWTH IS "Platinum"
LIST OCCUPATION_BADGES WHERE COMPANY IS "Amazon" AND TOTAL_POSTING_TIER IS "high"
LIST SKILL_ADJACENT_CLUSTERS FOR CLUSTER "187" WHERE RETENTION_RATE_3YR >= 0.7 EXPAND NAMES,COMPANY_HIRING
LIST SKILL_ADJACENT_CLUSTERS FOR CLUSTER "435" EXPAND NAMES FORMAT MERMAID
LIST DESTINATION_CLUSTERS FOR OCCUPATION WHERE ONET_CODE IS "15-1252.00"
LIST EARLY_CAREER_COMPANIES WHERE INDUSTRY IS "Healthcare"
GET¶
Subjects: COMPANY OCCUPATION CLUSTER WAGES WAGE_PROGRESSION
PROBABILITIES AI_IMPACT EDUCATION PATHWAYS BADGES HIRING RETENTION
LOCATION_ZIP LOCATION_CBSA.
Removed subjects. MOBILITY_PATTERNS, CAREER_TRANSITIONS,
ACCESS_REQUIREMENTS and RETENTION_DATA returned generated placeholder
values, not measured data. All four answer 410 Gone with
error_type: command_removed and name a replacement:
| Removed | Use instead |
|---|---|
MOBILITY_PATTERNS |
LIST DESTINATION_CLUSTERS, LIST SKILL_ADJACENT_CLUSTERS |
CAREER_TRANSITIONS |
LIST DESTINATION_CLUSTERS, GET PATHWAYS FOR OCCUPATION |
ACCESS_REQUIREMENTS |
GET EDUCATION FOR OCCUPATION |
RETENTION_DATA |
GET RETENTION FOR CLUSTER |
GET COMPANY "Microsoft"
GET COMPANY WHERE NAME IS "Apple"
GET BADGES FOR COMPANY WHERE COMPANY IS "Microsoft"
GET OCCUPATION WHERE ONET_CODE IS "15-1252.00"
GET WAGES FOR OCCUPATION WHERE ONET_CODE IS "15-1252.00" AND MSA_SIZE IS "Large" AND EXPERIENCE_LEVEL IS "5"
GET WAGE_PROGRESSION FOR OCCUPATION WHERE ONET_CODE IS "15-1252.00"
GET AI_IMPACT FOR OCCUPATION WHERE ONET_CODE IS "13-2011.00"
GET AI_IMPACT FOR CLUSTER "187"
GET EDUCATION FOR OCCUPATION WHERE ONET_CODE IS "15-1252.00"
GET PATHWAYS FOR OCCUPATION WHERE ONET_CODE IS "15-1252.00"
GET CLUSTER 187 INCLUDE OCCUPATIONS
GET WAGES FOR CLUSTER WHERE CLUSTER_ID IS 187
GET HIRING FOR COMPANY WHERE COMPANY IS "Amazon" AND CLUSTER_ID IS 187
GET LOCATION BY ZIP WHERE ZIP IS "98101"
GET LOCATION BY CBSA WHERE CBSA IS "41860"
COUNT / SEARCH / ANALYZE / VERSION¶
COUNT COMPANIES WHERE INDUSTRY IS "Healthcare" WHERE OVERALL_BADGE IS "Platinum"
COUNT OCCUPATIONS WHERE CLUSTER_ID IS 187 WHERE ENTRY_BARRIER_TREND IS "Rising"
COUNT COMPANY_BADGES GROUP BY archetype
COUNT OCCUPATION_BADGES GROUP BY industry
SEARCH COMPANIES WHERE SEARCH IS "micro" LIMIT 10
ANALYZE BADGES
VERSION
COUNT ... GROUP BY archetype returns the 3x3 archetype x tier matrix in one call.
TITLECONVERT¶
Resolve a job title to O*NET occupation(s) with 3-tier disambiguation. Adding context (especially a company) sharply improves accuracy.
TITLECONVERT "style guru" # basic (may be ambiguous)
TITLECONVERT "style guru" WHERE COMPANY IS "Great Clips" # ~95% (best)
TITLECONVERT "style guru" WHERE INDUSTRY IS "beauty" # ~85%
TITLECONVERT "manager" WHERE SCORE >= 0.7
TITLECONVERT "software engineer" LIMIT 10 GROUP BY ONET # unique O*NET codes
TITLECONVERT "manager" CLARIFY # returns clarification options
TITLECONVERT "software engineer" EXPAND CLUSTER # +cluster_id / cluster_name
Accuracy with context: title alone ~65% - +company ~95% (best) - +industry ~85% - +location ~70%.
Batch form (DSL-only)¶
Resolve up to 200 titles in one call. Runs FULLTEXT + RAG only (skips the Tier-3 LLM), so large lists stay fast.
TITLECONVERT ["nurse", "welder", "data scientist"] EXPAND CLUSTER
TITLECONVERT ["nurse", "manager"] WHERE INDUSTRY IS "Healthcare"
A single shared WHERE COMPANY IS "..." / WHERE INDUSTRY IS "..." applies to
the whole list. Response:
{
"results": [
{"title": "nurse", "recommended": {"onet_code": "...", "onet_title": "...", "score": 1.0},
"matches": [ ... ], "ambiguous": false}
],
"count": 3, "unique_count": 3, "ambiguous_count": 0,
"skipped_llm": true, "guidance": "..."
}
Items that come back ambiguous: true can be re-run individually (single-title
form) for full Tier-3 LLM disambiguation. This batch form is not available on
REST.
When a person (not the matcher) chooses one of the returned rows, record that
choice with SUBMIT TITLECONVERT — do not call
TITLECONVERT again.
Experimental matcher toggles (USING)¶
Experimental - unstable
These toggles expose the internal matching pipeline for tuning and testing. They are experimental and subject to change or removal. When omitted, the server uses its configured defaults. Most clients should not set them.
TITLECONVERT "nurse" USING NO FAST # force the legacy path
TITLECONVERT "nurse" USING CATALOG, NO FUSION # multiple toggles
Syntax: bare flag = ON - NO <flag> = OFF - <flag>=false explicit - comma-separated.
USING flag |
Type | Meaning (REST equivalent) |
|---|---|---|
FAST |
bool | Fast typeahead over the survey name corpus, default ON for no-company (?_fast) |
CATALOG |
bool | Catalog matcher (?_catalog) |
FUSION |
bool | Fusion ranking, default ON (?_fusion) |
BYPASS |
bool | Bypass disambiguation (?_bypass) |
FTS_AND |
bool | AND-mode full-text (?_fts_and) |
STEM |
bool | English stemming, default ON (?_stem) |
FUSION_ALPHA / FUSION_BETA / FUSION_EXACT |
float | Fusion weights |
FUSION_LEX_MODE |
string | Lexical calibration mode (minmax | percentile) |
COMPANYCONVERT¶
Fuzzy company resolution (exact -> alias -> prefix -> full-text).
Each match returns company_name, company_uid (or "NOT_IN_DATABASE"),
in_database, match_score, match_type (exact | alias | prefix |
suggest | fulltext), and confidence.
Handle NOT_IN_DATABASE
If in_database is false / company_uid is "NOT_IN_DATABASE", do not
chain downstream badge/occupation/wage lookups on that result.
CLUSTERCONVERT (legacy)¶
Legacy - prefer TITLECONVERT ... EXPAND CLUSTER
CLUSTERCONVERT is being phased out. Use TITLECONVERT "..." EXPAND CLUSTER,
which runs the full disambiguation pipeline.
SUBMIT SAMPLE¶
An open learning channel available to any dsl:execute caller.
SUBMIT SAMPLE SOURCE "intake-form" VALUE "widget wrangler"
SUBMIT SAMPLE ID "5f9b..." SOURCE "intake-form" VALUE "widget wrangler"
ID is optional (the server returns a v4 UUID). Idempotent on the GUID: a
duplicate returns recorded: false with no error. VALUE is parsed last, so it
may itself contain the words ID / SOURCE / VALUE.
SUBMIT TITLECONVERT¶
Record which occupation the user picked from a prior TITLECONVERT result.
This is a second sequential query — do not re-run TITLECONVERT.
CHOICE is the onet_code from the chosen row (ONET is a synonym).
TITLECONVERT "warehouse"
SUBMIT TITLECONVERT TITLE "warehouse" CHOICE "41-2031.00"
SUBMIT TITLECONVERT TITLE "warehouse" CHOICE "41-2031.00" AS "Retail Salespersons"
SUBMIT TITLECONVERT ID "550e8400-e29b-41d4-a716-446655440000" TITLE "warehouse" CHOICE "41-2031.00"
To send the same JSON REST posts (so cluster_id / cluster_name on an
EXPAND CLUSTER row are stored on picked), use VALUE. Quote the JSON with
single quotes so the object can use double quotes:
SUBMIT TITLECONVERT VALUE '{"query_info":{"title":"warehouse"},"picked":{"onet_code":"11-3071.00","cluster_id":42,"cluster_name":"…"}}'
SUBMIT TITLECONVERT TITLE "warehouse" VALUE '{"onet_code":"11-3071.00","cluster_id":42}'
TITLE is the string that was searched. AS is optional (onet_title from the
chosen row). ID is optional (the server returns a v4 UUID). Idempotent on the
GUID, same as SUBMIT SAMPLE. Open to any dsl:execute caller — not admin-gated.
Intake only: stored, not yet used in ranking. onet_code is still required
inside picked (cluster fields are extra, not a substitute).
REST twin: POST /api/titleconvert with the GET
envelope plus picked set to the chosen data[] row. POST only (the proxy
caches GET).
Admin writes (admin role)¶
Admin role only
These mutate data and require the admin role.
UPDATE COMPANY "x" SET NAME = "y"
REMOVE OCCUPATION "occ" FROM COMPANY "co"
REMOVE OCCUPATIONS ("a","b") FROM COMPANY "co"
REMOVE ALIAS "alias" FROM COMPANY "co"
ADD ALIAS "alias" TO COMPANY "co" TYPE former_name
Alias TYPE: alternate_spelling | former_name | abbreviation |
parent_company | subsidiary | legal_name | typo.
HELP¶
HELP returns a guided index; HELP <topic> drills in. Topics: COMPANIES
WAGES OCCUPATIONS CONVERT HIRING AI_IMPACT AI_IMPACT_EXPERIMENTAL
CLUSTERS PATHWAYS METHODOLOGY SCHEMA BADGES (COMPANY_BADGES /
OCCUPATION_BADGES) GUIDELINES / BEHAVIOR SAMPLE.
WHERE fields by subject¶
- COMPANIES:
INDUSTRYINDUSTRY_IDOVERALL_BADGEBADGE_EARLY_CAREERBADGE_GROWTHBADGE_STABILITYEVENT_TYPECOMPANY_UIDNAME/COMPANY_NAMESTATECITYCOUNTRYSEARCHANY_BADGEALL_BADGESBADGE_COUNT - OCCUPATIONS:
SEARCHCLUSTER_IDJOB_LEVELONET_CODECOMPANY_COUNTAI_FLAGENTRY_BARRIER_TRENDEXPERTISE_PREMIUM_TREND - CLUSTERS:
SEARCHCLUSTER_IDJOB_LEVELPREMIUM_SKILLAI_FLAGENTRY_BARRIER_TRENDEXPERTISE_PREMIUM_TREND - COMPANY_BADGES:
INDUSTRYOVERALL_BADGEBADGE_*STATECITYCOUNTRY - OCCUPATION_BADGES:
BADGE_*COMPANY/COMPANY_NAMEINDUSTRYCLUSTER_IDONET_CODEENTRY_POSTING_TIERTOTAL_POSTING_TIERTOP10 - WAGES:
ONET_CODECLUSTER_IDMSA_SIZEEXPERIENCE_LEVEL - Companies FOR CLUSTER:
ENTRY_POSTING_TIERTOTAL_POSTING_TIERBADGE_*INDUSTRYTOP10CBSAHIRING_POSTINGS_QTILE - TITLECONVERT:
COMPANYSECTOR/INDUSTRYSCORE(maps to min_score) - COMPANYCONVERT:
SECTOR/INDUSTRYSCORE - CLUSTERCONVERT:
JOB_LEVELAI_FLAGSCORE
Deprecated
POSTINGS_QTILE was removed in 0.13.0 and now returns a -32602 error. Use
ENTRY_POSTING_TIER / TOTAL_POSTING_TIER and ORDER BY ENTRY_POSTING_COUNT
/ TOTAL_POSTING_COUNT.
Enums and controlled vocabularies¶
- Badges (
overall_badge,badge_early_career,badge_growth,badge_stability):Platinum|Gold|NA(null). Filter sentinelRanked= Platinum or Gold. - AI_FLAG:
Raising the Bar|Lower Potential|Shrinking Fields|Winners Pull Away - Trends (
entry_barrier_trend,expertise_premium_trend):Rising|Falling - JOB_LEVEL:
Advanced Degree|Bachelor's Degree|Bachelor's Degree + Experience|Sub-Bachelor's Degree|Sub-Bachelor's Degree + Experience - Posting tiers (
entry_posting_tier,total_posting_tier):high|above-average|average|low|none - HIRING_POSTINGS_QTILE:
1|2|3(geographic; distinct from posting tiers) - MSA_SIZE:
Large|Small/Medium- EXPERIENCE_LEVEL:0|5|10(years) - TOP10:
1(legacyY/Nnormalized to0/1) - EVENT_TYPE:
acquired|acquired_still_operating|merged|rebranded|split|chapter_11_bankruptcy|none
Errors¶
DSL errors use JSON-RPC-style codes:
-32602invalid params / deprecated feature-32603internal error
Common cases and fixes:
- Token expired ->
POST /auth/login - Invalid DSL syntax -> check quoting; remove pipe operators (
|) - "...not in our database" -> resolve with
COMPANYCONVERT "<x>"first - "No title matches" -> lower the score threshold or broaden the title
POSTINGS_QTILEdeprecation -> use the posting-tier fields (above)
What DSL cannot do (use REST)¶
DSL is a superset, but a few capabilities remain REST-only:
- Version compare -
POST /api/version/compare. - Meta / infra -
/health,/changelog,/api/version(not data queries).
Closing these gaps so DSL stays a strict superset is the standing direction.