Skip to content

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).
  • Ranked is a badge sentinel meaning any non-NA badge (Platinum or Gold); it may be written bare or quoted.

Response envelope

{ "status": "success", "data": [ ... ], "count": 12 }

Errors are structured to guide recovery:

{ "error": { "code": -32602, "message": "...", "suggestion": "...", "example": "..." } }

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-end
  • ORDER BY <field[,field]> [ASC|DESC]
  • GROUP BY ONET | <badge dimension>
  • EXPAND <token[,token]> - INCLUDE OCCUPATIONS - WITH SCORE
  • FORMAT JSON | MERMAID (adjacency subjects only - see below)
  • USING <flags> (experimental matcher toggles - see below)
  • CLARIFY (TITLECONVERT)

ORDER BY fields depend on the subject:

  • Companies: PLATINUM GOLD BADGE_COUNT ALPHA ALPHA_DESC INDUSTRY RELEVANCE COMPANY_COUNT
  • Occupations: TITLE ONET_CODE CLUSTER_ID CLUSTER_NAME AI_FLAG ENTRY_BARRIER_TREND EXPERTISE_PREMIUM_TREND JOB_LEVEL COMPANY_COUNT_*
  • Clusters: CLUSTER_NAME CLUSTER_ID AI_FLAG <trend> OCCUPATION_COUNT BA_PLUS_SHARE
  • Industries: INDUSTRY COMPANY_COUNT
  • Companies for a cluster: ALPHA ENTRY_POSTING_COUNT TOTAL_POSTING_COUNT BADGE INDUSTRY WYWM (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. Explicit FORMAT JSON is a no-op.
  • MERMAID — adds a mermaid diagram string plus mermaid_note alongside the rows, on LIST SKILL_ADJACENT_CLUSTERS and LIST DESTINATION_CLUSTERS only. Node ids are cluster ids, so any node can be fed back into FOR CLUSTER "<id>". Pair with EXPAND NAMES to 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).

COMPANYCONVERT "MSFT"
COMPANYCONVERT "apple" WHERE INDUSTRY IS "Software & Technology"

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.

CLUSTERCONVERT "nurse" WHERE JOB_LEVEL IS "Sub-Bachelor's Degree" WHERE SCORE >= 0.5 LIMIT 5

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: INDUSTRY INDUSTRY_ID OVERALL_BADGE BADGE_EARLY_CAREER BADGE_GROWTH BADGE_STABILITY EVENT_TYPE COMPANY_UID NAME/COMPANY_NAME STATE CITY COUNTRY SEARCH ANY_BADGE ALL_BADGES BADGE_COUNT
  • OCCUPATIONS: SEARCH CLUSTER_ID JOB_LEVEL ONET_CODE COMPANY_COUNT AI_FLAG ENTRY_BARRIER_TREND EXPERTISE_PREMIUM_TREND
  • CLUSTERS: SEARCH CLUSTER_ID JOB_LEVEL PREMIUM_SKILL AI_FLAG ENTRY_BARRIER_TREND EXPERTISE_PREMIUM_TREND
  • COMPANY_BADGES: INDUSTRY OVERALL_BADGE BADGE_* STATE CITY COUNTRY
  • OCCUPATION_BADGES: BADGE_* COMPANY/COMPANY_NAME INDUSTRY CLUSTER_ID ONET_CODE ENTRY_POSTING_TIER TOTAL_POSTING_TIER TOP10
  • WAGES: ONET_CODE CLUSTER_ID MSA_SIZE EXPERIENCE_LEVEL
  • Companies FOR CLUSTER: ENTRY_POSTING_TIER TOTAL_POSTING_TIER BADGE_* INDUSTRY TOP10 CBSA HIRING_POSTINGS_QTILE
  • TITLECONVERT: COMPANY SECTOR/INDUSTRY SCORE (maps to min_score)
  • COMPANYCONVERT: SECTOR/INDUSTRY SCORE
  • CLUSTERCONVERT: JOB_LEVEL AI_FLAG SCORE

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 sentinel Ranked = 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 (legacy Y/N normalized to 0/1)
  • EVENT_TYPE: acquired | acquired_still_operating | merged | rebranded | split | chapter_11_bankruptcy | none

Errors

DSL errors use JSON-RPC-style codes:

  • -32602 invalid params / deprecated feature
  • -32603 internal 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_QTILE deprecation -> 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.