Skip to main content

Career Navigation API

These additional v2 routes read and update participants' Career Navigation data. Existing /career-navigation-profiles routes and all other v2 contracts continue to behave as documented.

Use the standard Authorization: Bearer sk_live_... or sk_test_... header. Every participant resource is restricted to the API key's account. A profile ID is the lookup code returned by /career-navigation-profiles, not the numeric test taker ID. URL-encode IDs when building requests. Bodies must be JSON; unexpected body properties are rejected. Account and participant ownership cannot be changed through these routes.

All paths below are relative to /api/v2. Responses use { "mode": "live", "object": "...", "data": ... }. Public response fields generally use snake_case; the stored plan-job objects retain jobName, onetCode, onetOccupation, educationTraining, credentials, and supportFromOthers, and exercise-answer keys retain their documented camelCase names. Profile mutations return the same profile id used by snapshots. Request JSON retains the documented exercise/plan field names. List data is always an array. List responses also include total_count and has_more: false. Responses are private and must not be cached publicly. Protect profile IDs and participant links as access capabilities.

Routes​

MethodPathPurpose
GET/career-navigation/profiles/:idFull navigation snapshot and save revisions
PUT/career-navigation/profiles/:id/locationSet job search ZIP code
PATCH/career-navigation/profiles/:id/exercisesUpdate selected exercise answers
PUT/career-navigation/profiles/:id/planReplace all three plan jobs
PUT/career-navigation/profiles/:id/compareReplace three comparison selections
GET/career-navigation/profiles/:id/favorites/occupationsFavorite occupation codes
GET/career-navigation/profiles/:id/favorites/occupations/:onetCodeRead favorite state
PUT/career-navigation/profiles/:id/favorites/occupations/:onetCodeSet favorite state
GET/career-navigation/profiles/:id/job-familiesBatch family and occupation scores
GET/career-navigation/profiles/:id/job-families/topStandalone top-family scores
GET/career-navigation/job-families/:familyNumberGlobal system family reference
GET/career-navigation/profiles/:id/occupations/:onetCode?family_number=NNNParticipant and occupation interest letters
POST/career-navigation/profiles/:id/certifications/occupationCreate or reuse an occupation certificate
GET/career-navigation/profiles/:id/job-fit-profileParticipant interests and behavioral strengths

| GET | /career-navigation/profiles/:id/job-fit-report | Full report results and all ranked job families |

Profiles remain provisioned through assessments. These routes do not create/delete profiles or delete certificates. Unsetting a favorite removes it from the favorite list. Clearing exercise strings or plan job fields uses the corresponding update route.

Profile snapshot​

GET /career-navigation/profiles/:id returns object: "career_navigation_profile". Its data contains:

  • id, created_at, updated_at, numeric test_taker_id, first_name, last_name, email.
  • team and account, each with id and display_name.
  • account_occupations: occupation visibility settings with onetsoc_code and visibility.
  • job_search_zip_code, favorite_occupations, and compare_onetsoc_code_1, compare_onetsoc_code_2, compare_onetsoc_code_3.
  • exercises (nullable answer map), plan_job_1, plan_job_2, plan_job_3, and revisions: { exercises, plan }.
  • assigned_assessments with id, completed_at, and assessment: { display_name, short_name }.
  • certifications (certificate interest riasec_letters may be null when unavailable), career_navigation_url, and nullable plan_url for sharing a plan.

The exercises and plan revisions are opaque strings. Read the snapshot before saving and use the appropriate revision. A successful save returns a replacement revision. A 409 revision_conflict means the stored value changed: reload and reconcile the user's changes before retrying. Do not repeatedly retry the stale revision.

Job Fit Profile​

GET /career-navigation/profiles/:id/job-fit-profile returns object: "career_navigation_job_fit_profile" with private, non-cacheable participant results:

  • id, numeric test_taker_id and account_id, first_name, and last_name.
  • interests: ordered RIASEC results, each with key, name, descriptor, and description.
  • traits: up to five ordered behavioral strengths, each with key, name, and description.

Descriptions use the English one-page Job Fit PDF content and the same trait selection logic. Empty arrays indicate unavailable assessment results; consumers should display an explanatory state. The existing PDF report key remains jfs. Access follows the same account and participant ownership checks as the profile snapshot.

Job Fit Report​

GET /career-navigation/profiles/:id/job-fit-report returns object: "career_navigation_job_fit_report". It uses the same account and participant ownership checks and private, non-cacheable responses as Job Fit Profile.

  • id, numeric test_taker_id and account_id, first_name, and last_name.
  • assessments: assessment name and completed_at, including only completed assessments used for scoring and excluding prescreen assessments, matching the PDF report.
  • profile: ordered interests and traits, each with key, name, and description. Interest descriptions use the full English PDF content.
  • details: interests and traits using the detailed dimension/facet objects from the existing Job Fit Report API, including score ranges and trait anchors.
  • families: all eligible job families, grouped by family number with the highest raw score retained, each with id, name, three-digit number, zone, percent, and stars.

Clients can implement the three PDF versions locally: filter families to zones 1–5, 1–3, or 3–5, sort by descending percent, then select the first 15 and number them from 1. The interests and traits do not change between versions. The PDF key remains jfr, with zones=1_5, 1_3, or 3_5. Empty result arrays indicate unavailable assessment results.

Location​

PUT /career-navigation/profiles/:id/location
{ "zipCode": "02110" }

Accepts five-digit ZIP or ZIP+4. Returns object: "career_navigation_location" and data: { id, job_search_zip_code }.

Exercises​

PATCH /career-navigation/profiles/:id/exercises
{
"changes": { "strengthsCareerInterests": "Investigative work" },
"revision": "COPY_FROM_PROFILE"
}

Only supplied answers change; other answers are retained. An empty string clears an answer. Returns object: "career_navigation_exercises" and data: { id, exercises, revision }.

Supported keys (string values):

strengthsCareerInterests, strengthsPersonalityTraits, strengthsJobFamilies,
explorationInterviewNotes, explorationInterviewReflection,
lmiTargetJobProjectedGrowth, lmiTargetJobCompensation, lmiTargetJobJobOpenings,
lmiTargetJobStartingWages, lmiTargetJobRequiredEducationTraining,
lmiTargetJobRequiredCredentials, lmiTargetJobEducationTrainingOptions,
challengesBarriers, challengesSupport, mentorsNames, mentorsGuidance,
targetJobName, targetJobOnetCode, targetJobFiveStarRating, actionPlanNextSteps,
transferableSkillsKnowledge, transferableSkillsSkills, transferableSkillsAbilities

Exercise answers are reflective text, not authoritative assessment scores.

Plan​

PUT /career-navigation/profiles/:id/plan
{
"job1": {
"jobName": "",
"onetCode": "",
"onetOccupation": "",
"educationTraining": "",
"credentials": "",
"supportFromOthers": ""
},
"job2": {
"jobName": "",
"onetCode": "",
"onetOccupation": "",
"educationTraining": "",
"credentials": "",
"supportFromOthers": ""
},
"job3": {
"jobName": "",
"onetCode": "",
"onetOccupation": "",
"educationTraining": "",
"credentials": "",
"supportFromOthers": ""
},
"revision": "COPY_FROM_PROFILE"
}

All three jobs and all six string fields per job are required. Nonempty O*NET codes must identify an occupation; free-text job names are supported. Returns object: "career_navigation_plan" and data: { id, plan_job_1, plan_job_2, plan_job_3, revision }.

Comparison selections​

PUT /career-navigation/profiles/:id/compare
{
"compare_onetsoc_code_1": "15-1252.00",
"compare_onetsoc_code_2": null,
"compare_onetsoc_code_3": null
}

All three fields are required. Valid occupation codes select an occupation; null or an empty string clears a slot. Returns object: "career_navigation_comparison" with the three stored columns and id.

Favorites​

GET the favorite list returns object: "list" with data as an array of { onetsoc_code, is_favorite: true } entries. GET one occupation returns object: "career_navigation_favorite" and data: { onetsoc_code, is_favorite }.

PUT /career-navigation/profiles/:id/favorites/occupations/15-1252.00
{ "isFavorite": true }

PUT sets the requested boolean state and returns the same response shape as GET. Setting false unsets a favorite. Repeating the same request preserves the state; it does not toggle it.

Scoring and reference data​

Family numbers contain exactly three digits; O*NET codes follow NN-NNNN.NN.

The batch scoring route returns object: "career_navigation_job_family_scores" and data: { id, scores_by_job_family_number, scores_by_onet_occupation, top_job_families }. Family entries contain raw_score, job_fit_percentage, star_score, and score_profile: { id, display_name, short_name }. Occupation entries contain those three score fields and score_profile_short_name. Family variants are grouped by family number using the highest raw score; the top 15 families are ranked by raw score.

The standalone top route returns object: "list" and up to 15 entries with id, raw_score, job_fit_percentage, star_score, short_name, and display_name. It preserves the standalone Job Fit ranking rules: score-profile variants are ordered by job_fit_percentage descending, excluded special families are removed, and the first 15 variants are returned without grouping by family number. Its ranking and family count can differ from the batch results. Consumers must choose the route that matches their product meaning; do not treat these lists as interchangeable. Scoring requires completed Career Interests results; the batch route also requires those results to be enabled for scoring. Unavailable scores return 409 scores_not_ready.

The global family reference route returns object: "career_navigation_job_family" and data: { id, display_name } for the system family (the first system variant ordered by short name, then ID). This new global reference does not change the account-enabled catalog returned by the existing /score-profiles route.

Occupation interest comparison returns object: "career_navigation_occupation" and data: { participant, occupation }. participant contains first_name, last_name, email, account_name, and riasec_letters; occupation contains onetsoc_code and riasec_letters.

Occupation certificates​

POST /career-navigation/profiles/:id/certifications/occupation
{ "onetsoc_code": "15-1252.00", "family_number": "123" }

The occupation must belong to a system profile variant within the requested family. Matching variants are selected deterministically by short name, then ID. Returns object: "career_navigation_certificate" and the certificate record, with 201 when created or 200 when an existing certificate is reused. Repeated and concurrent calls to this new endpoint use a stable certificate identifier. Creating a new record invokes the existing Hasura certificate-created event trigger, whose notification handler sends email and may send SMS according to the existing configuration. Reusing an existing record does not insert a row or retrigger creation notifications. Concurrent creation through legacy routes is outside this endpoint's deduplication guarantee.

Test keys​

Valid sk_test keys return deterministic synthetic responses with mode: "test". They never read real participants, persist navigation changes, or create real certificates. Request validation still applies. Any syntactically valid profile ID may be used; GET returns Sample Participant (test taker 12345, sample account 200, sample team 100). Exercise answers and plan fields begin empty. Use revisions from that GET for exercises/plan saves; stale fixture revisions return 409. Writes return synthetic results without persisting them, so the next GET returns the original fixture. Certificate POST always returns a synthetic 201; it does not simulate deduplication. Family/scoring fixtures are illustrative and do not represent production ranking.

Errors​

{
"mode": "live",
"error": { "code": "resource_not_found", "message": "Resource not found" }
}
StatusCodeMeaning
400invalid_requestInvalid fields, formats, or family/occupation association
401invalid_credentialsMissing or invalid Bearer key
404resource_not_foundMissing resource or participant outside the key's account
405method_not_allowedUnsupported method; see Allow header
409revision_conflictReload and reconcile before saving
409scores_not_readyRequired assessment scores are unavailable
503service_unavailableAuthentication or certificate-creation configuration temporarily unavailable
500internal_errorRequest could not be completed

Unauthenticated errors omit mode. Missing and other-account participants have the same response.

Download the additional Career Navigation Postman collection. Set your API URL, customer key, profile ID, family number, and occupation code. Read a profile to populate save revisions first.

Contract fixtures and website recovery​

Versioned synthetic response examples are generated from the API fixture operations and public serializer with yarn contract:career-navigation. The producer tests compare the artifact to current output. Career Navigation keeps a copy and runs its response parsers against it; update both copies together when an intentional compatible contract change ships. Fixtures illustrate wire shapes and do not verify live ranking or persistence.

The website validates occupation existence using its O*NET dataset before saving a plan. A lookup outage is a temporary failure, not evidence that a code is invalid. The public API retains its existing error behavior, including 404 for a missing occupation. Following an uncertain exercise or plan save, the website reads back the profile and accepts the write only when the submitted values match. It retains later local edits and uses the returned revision for the next save. A genuine 409 revision_conflict never overwrites a newer draft automatically.