Skip to content

API Reference (Technical)

The WorkWingman backend is an ASP.NET Core app exposing a REST API that the Angular frontend (and Electron shell) call. This page documents every endpoint, grouped by controller.

Source of truth: src/WorkWingman.Api/Controllers/*.cs and the models in src/WorkWingman.Core/Models/. If this page and the code disagree, the code wins — fix the page (see the two-track docs rule in contributing.md).

Conventions

  • Base URL: http://127.0.0.1:5211loopback only. The API binds to 127.0.0.1, never 0.0.0.0, so it is only reachable from the same machine.
  • JSON casing: request and response properties are camelCase.
  • Enums serialize as strings (e.g. "Queued", "AwaitingJudgement", "TopApplicant"), not integers.
  • Auth: most endpoints are unauthenticated (loopback trust). The one state-changing install endpoint is token-gated — see Dependencies.
  • Secrets are never returned over HTTP. The vault exposes metadata only.

Model types referenced below live in src/WorkWingman.Core/Models/.

Jobs — JobsController (/api/jobs)

Saved LinkedIn jobs, queued oldest-first.

Method Path Body Returns Notes
GET /api/jobs JobPosting[] The full queue, oldest-first.
GET /api/jobs/{jobId} JobPosting 404 if unknown.
POST /api/jobs/resync int Re-syncs from the LinkedIn jobs tracker; returns the count of new jobs.

JobPosting carries the LinkedIn Premium Fit assessment (FitLevel, matched/gap keywords), Ats info (AtsKind, apply URL, whether an account exists in the vault), a JobStatus, LayoffHistory (see job-signals.md), and a CostOfLiving snapshot for the job's city (see col-tax.md) — populated on resync, null until enrichment runs or when the location can't be resolved to a city/state.

| GET | /api/jobs/{jobId}/impact | — | JobImpact | "What changes for you" — see financial-profile. 404 if the job is unknown; every field on the result may be null when there isn't enough data to compute it (that's normal, not an error). |

Runs — RunsController (/api/runs)

A live ATS automation run. Nothing submits without user review.

Method Path Body Returns Notes
GET /api/runs/active ApplicationRun 204 No Content when no run is active.
POST /api/runs/start/{jobId} ApplicationRun Starts a run for the job.
POST /api/runs/{runId}/pause 204
POST /api/runs/{runId}/resume 204
POST /api/runs/{runId}/stop 204
POST /api/runs/{runId}/resolve { callId, choice } 204 Resolves the pending JudgementCall; the choice persists as a reusable fill rule.

An ApplicationRun exposes Status (RunStatus), Steps, a Log, the CurrentUrl, CurrentReasoning, and a PendingCall (JudgementCall) when the run has paused below the auto-fill confidence threshold and needs the user to choose from ranked options.

Documents — DocumentsController (/api/documents)

Tailored resume + cover letter for one job. Always reviewed before use.

Method Path Body Returns Notes
GET /api/documents/{jobId} GeneratedDocuments 404 if none generated yet.
POST /api/documents/{jobId}/generate GeneratedDocuments Generates the tailored docs.
POST /api/documents/{jobId}/regenerate GeneratedDocuments Regenerates (new version).
POST /api/documents/{jobId}/approve 204 Marks the docs approved.

GeneratedDocuments holds a Resume and CoverLetter (TailoredDocument, with version + change summary), an Approved flag, and CopyPasteFields for manual-apply flows.

Study — StudyController (/api/study)

Study & prep resources matched to a job description.

Method Path Body Returns Notes
GET /api/study/{jobId} StudyPlan Resources + gap analysis.
POST /api/study/{jobId}/claude-project string Creates a Claude study project; returns its URL.
POST /api/study/{jobId}/enroll { resourceUrl } 204 Enrolls via a linked learning-platform account.

Profile — ProfileController (/api/profile)

The reusable answers behind every application.

Method Path Body Returns Notes
GET /api/profile/intake IntakeProfile Contact, eligibility, demographics, education, work history, skills.
PUT /api/profile/intake IntakeProfile 204 Saves the intake profile.
GET /api/profile/story StoryProfile Story prompts + writing samples + voice summary.
PUT /api/profile/story StoryProfile 204 Saves the story profile.
GET /api/profile/financial FinancialProfile Optional salary/insurance history — see financial-profile. Every field defaults to empty/null.
PUT /api/profile/financial FinancialProfile 204 Saves the financial profile. Local-only — never mirrored to Drive, unlike intake/story.

Self-assessment — AssessmentController + AssessmentFormsController (/api/assessment)

Optional personality/work/communication/learning-style suite. Everything under /api/assessment is local-only (see self-assessment.md) except the last row, which is a deliberately separate controller/route because it's the one path that leaves the machine.

Method Path Body Returns Notes
GET /api/assessment AssessmentProfile Whole profile; every section nullable until taken.
GET /api/assessment/ipip-items IpipItem[] The 50 IPIP Big Five items, for rendering the questionnaire.
POST /api/assessment/big-five { itemNumber: rating } × 50 BigFiveResult Scores + saves; 400 if any item missing or rating outside 1-5.
PUT /api/assessment/mbti SelfReportedMbti 204 Self-entered 4-letter type from an external free test.
PUT /api/assessment/work-style WorkStyleResult 204
PUT /api/assessment/communication-style CommunicationStyleResult 204
PUT /api/assessment/learning-style LearningStyleResult 204
GET /api/assessment/resources AssessmentResource[] Curated external-test links (16Personalities, etc.).
POST /api/assessment/export/google-forms string (responder URL) Leaves the machine — mirrors the profile into a new Google Form. Only call from an explicit user action.

Applications — ApplicationsController (/api/applications)

Submitted-application tracking and interviews.

Method Path Body Returns Notes
GET /api/applications ApplicationRecord[] All tracked applications.
GET /api/applications/interviews Interview[] Upcoming interviews.
PUT /api/applications/{recordId}/status { status } 204 status is an ApplicationStatus string.
POST /api/applications/sync-interviews int Scans Gmail for interview invites, syncs to Google + Apple calendars; returns the count synced.

Finances — FinancesController (/api/finances)

Cost-of-living snapshots, income-tax estimates, and city-to-city comparisons — see col-tax.md. Tax figures are estimates from a deterministic local dataset, not tax advice.

Method Path Body/Query Returns Notes
GET /api/finances/cost-of-living?city=&state= CostOfLivingSnapshot 400 if city/state missing. Nullable fields — sources degrade gracefully.
POST /api/finances/tax-estimate { gross, state, city?, filingStatus? } TaxEstimate 400 on an unrecognized state code. Pure/deterministic, no network.
POST /api/finances/compare { a: CitySalary, b: CitySalary } CityComparisonResult Side-by-side take-home + COL-adjusted delta for two (salary, city) pairs.

Connections — ConnectionsController (/api/connections)

Linked accounts and automation settings.

Method Path Body Returns Notes
GET /api/connections Connection[] LinkedIn, Google, Apple, vault, Claude, learning platforms, GitHub, calendars — each with a ConnectionState.
GET /api/connections/settings AutomationSettings Confidence threshold, start-from-blank, encouragement level, data directory.
PUT /api/connections/settings AutomationSettings 204 Saves settings. Note AlwaysRequireReviewBeforeSubmit is locked on.

Vault — VaultController (/api/vault)

KeePass vault. Metadata only — no endpoint ever returns a password; secrets are resolved in-process by the automation engine.

Method Path Body Returns Notes
GET /api/vault/status { unlocked: bool } Whether the vault is unlocked.
POST /api/vault/unlock 204 Unlocks the vault.
GET /api/vault/entries VaultEntryInfo[] Entry metadata (name, category, username, URL, modified) — never secrets.

Dependencies — DependenciesController (/api/dependencies)

The dependency doctor.

Method Path Body Returns Notes
GET /api/dependencies DependencyReport Read-only probe of every dependency (installed/missing/outdated + summary). Runs every launch.
POST /api/dependencies/install { approvedIds } InstallResult[] Token-gated. Installs/repairs only the approved ids, then re-verifies each.

POST /install is state-changing (it runs shell installers via winget/npm/Playwright), so it requires the X-WorkWingman-Token header or a trusted dev origin — checked by ApiToken.IsTrusted in src/WorkWingman.Api/Security/ApiToken.cs. Requests that aren't trusted get 401; an empty approvedIds list gets 400. There is deliberately no "install everything" path — the caller passes the explicit ids the user approved.

curl examples

Get the job queue (loopback):

curl http://127.0.0.1:5211/api/jobs

Resolve a paused judgement call in a run (note the string enum body and camelCase):

curl -X POST http://127.0.0.1:5211/api/runs/RUN_ID/resolve \
  -H "Content-Type: application/json" \
  -d '{"callId":"CALL_ID","choice":"Yes, authorized to work in the US"}'

Install approved dependencies (token-gated — this call fails without the header):

curl -X POST http://127.0.0.1:5211/api/dependencies/install \
  -H "Content-Type: application/json" \
  -H "X-WorkWingman-Token: YOUR_LAUNCH_TOKEN" \
  -d '{"approvedIds":["keepassxc","playwright-chromium"]}'