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:5211— loopback only. The API binds to127.0.0.1, never0.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"]}'
Related docs¶
- Plain-language version: ../plain/api-reference.md
- Getting started: getting-started.md
- Contributing: contributing.md
- Docs index: ../README.md