Skip to content

Local TypeScript API ​

The local Fastify API is the browser-facing boundary for JobCtrl. It serves projection-backed and canonical detail reads, accepts explicit commands, starts Temporal workflows, and streams domain-event invalidations to the web app.

Read this first for the shape of the API. Follow a route-family link for the common contracts, or open the complete field-level contract when implementing or debugging a specific endpoint.

In 30 Seconds ​

  • Personal reads come from SQLite-backed projections and authorized canonical detail state; clients do not reconstruct domain state from raw events.
  • Workflow-starting commands normally return 202 Accepted. That means queued, not completed.
  • Synchronous reads and commands return 200 OK; invalid input, unavailable workers, and start failures return an error instead of a misleading 202.
  • Browser updates arrive through GET /v1/events/stream; TanStack Query exact- patches truthful detail payloads and boundedly refetches affected projections when membership, aggregation, or missing event data requires reconciliation.
  • Credentials are handled through the local credential boundary, never through ordinary settings payloads.

Choose A Route Family ​

You need to…Start here
Read or update the candidate profile, preferences, discovery settings, or credentialsProfile & Settings
List jobs, inspect score/material evidence, review applications, or work with contactsJobs & Materials
Start/cancel workflows, inspect current pipeline operations, inspect health, or consume realtime eventsOperations & Events
Check every field, status code, precedence rule, and cadenceComplete Contract

API At A Glance ​

BoundaryRepresentative routesResponse model
Profile and configuration/v1/profile, /v1/settings, /v1/credentials, /v1/providers/models, /v1/discovery/settings, /v1/browser-capabilities, /v1/extension/pairing-token, /v1/discovery/browser-extension/statusSynchronous reads, validated patches, and live extension readiness
Career learning/v1/evidence-map/learningLocal recorded reads, no-model cohort preview and explicitly requested comparison/milestone commands
Jobs and evidence/v1/jobs, /v1/jobs/:jobKey, /v1/evidence-map, /v1/artifactsProjection-backed reads
Interviews/v1/interviews/catalog, /v1/interviews/questions/:questionId, per-job prep history and interview notesAsset-backed public reads; tenant/job-scoped prep and revisioned private notes
Scoring keywords and feedback learning/v1/scoring/keywords, /v1/learning/recommendations, /v1/learning/policies/materialsCurrent score-version aggregation plus explicit review and versioned policy history
Review and outcomes/v1/apply/review-queue, /v1/jobs/:jobKey/apply-review/decision, /v1/jobs/:jobKey/repeat-application/override, /v1/outcomesExplicit review commands plus read models
Workflow operations/v1/pipeline/actions/run-stage, /v1/pipeline/operations, /v1/workflow-runs, /v1/health202 for accepted asynchronous work; 200 for projection-backed and runtime-backed reads/sync commands
Realtime/v1/events/streamServer-Sent Events with replay and reconnect support

Dashboard source-health and digest blocked-source entries retain stable sourceId values and may include displayName from the source registry. Clients prefer that name, including current JobStreaming board labels for historical source keys. Names do not change source-health or quarantine facts. Digest job counts require the local tenant's canonical JobId, and date filters compare instants at millisecond precision. The explicit acknowledgement route serializes with Python writers and commits the watermark and review event together; passive digest reads do not acknowledge.

Profile And Preferences ​

Profile reads expose a bounded legacy-confirmation review notice and computed evidence authority flags. The private original/review history stays out of SSE; review decisions use the stopped-runtime local command. See the Profile & Settings contract.

Profile data, preferences, discovery controls, settings, and credentials have different owners even though the UI presents them together. See Profile & Settings for that ownership map and the routes used by autosave, resume preview, source administration, and native-store-backed credentials.

Target Search can request role suggestions through POST /v1/profile/target-role-suggestions. The web sends only the last saved profileVersion; it never sends the current browser form draft. The TypeScript API checks that version before and after one synchronous Python RPC call, and the worker builds a bounded payload from saved experience titles, achievement evidence, and skills. Contact details, voluntary EEO values, compensation, attestations, raw PDF data, and raw profile content logs are excluded. Results remain transient until the user edits/selects them and accepts an append through the normal PATCH /v1/profile path with expectedProfileVersion.

The RPC also carries app-directory and database identity derived from the API's trusted runtime context. The worker rejects a mismatch before it reads a snapshot. Current managed provider SDKs do not enforce the required output-token and maximum-call-cost bounds, so this production route makes no provider call. It returns deterministic direct titles from compatible saved experience and a small set of adjacent titles only when linked achievement evidence supports their full substantive wording; an empty result is valid. Historical location and work-model proposals come only from explicit experience location text. Every role and historical preference proposal starts unselected. The provider_token_or_cost_bound_unsupported warning records the provider gate. Synthetic adapter tests cover the model response validator without claiming a production model run; broader inference still requires bounded provider support and semantic validation.

Artifacts And Tailoring Audit ​

Artifact list/detail/preview routes and canonical tailoring evidence are grouped with job reads in Jobs & Materials. The complete contract documents every generation, template, provenance, layout, and audit field.

Jobs Read Model And Lifecycle ​

The jobs list supports globally paginated stages membership alongside legacy scalar stage. The shared API client serializes arrays as comma-separated query values, including an empty array as stages=. Membership, precedence, metadata, and bulk-selection semantics are owned by Jobs & Materials and the complete contract. The Jobs removed-selection action uses GET /v1/jobs/reviewable-list and POST /v1/jobs/permanent-delete-selection to bind the confirmed cohort to its observed lifecycle state before permanent deletion. The Operations browser adapter validates the raw stage array with the shared schema before merging stage-local pages. It preserves explicit empty arrays, scalar fallback for invalid or oversized arrays, and the requested scalar in response metadata. Its input contract accepts readonly stage arrays. Text sorts use lowercase locale comparison consistent with the browser merger. Discovery sorts preserve the browser's locale comparison of raw timestamp strings, including mixed fractional/offset notation, rather than instant order. Source/company filters match literal Unicode-lowercased substrings on summary-equivalent scalar values. All searches and sorts use SQLite selection over the app-owned, rebuildable query index. Timestamp cutoffs compare precomputed instants inclusively at JavaScript millisecond precision, including the OR of two cutoffs. Lists and all-matching bulk selection share those predicates. Index refresh, totals, page selection and selected-summary joins stay within one SQLite transaction; only the returned page receives DTO hydration, availability and template reads.

Jobs list/detail include Enrichment's persisted availability observation: latest attempt/verdict, last successful verification, next due, acquisition evidence and computed overdue/in-progress state. These GETs never fetch an employer posting. POST /v1/jobs/:jobId/actions/check-availability accepts an empty strict body and a canonical JobId, dispatches runtime-bound JSON-RPC check_posting_availability, and returns 202 with workflow/run IDs. JobAvailabilityObserved invalidates the selected detail and tenant job lists. Job-scoped AvailabilityLeaseChanged command-deferral records invalidate the same reads. Only explicit requests own coalesced deferral feedback; automatic and preflight refusals do not create user requests or employer observations. See Jobs & Materials and the field contract.

The jobs list and detail routes read stable projections. Lifecycle changes—hide, restore, delete, score correction, stage retry, and per-job actions—are explicit commands. A stale-score reset leaves the accepted score visibly stale while replacement work is pending. See Jobs & Materials.

On job detail, Enrich stage summaries can carry a separate, allow-listed applyUrlOutcome (code, message, retryable, and method). It reports application-target readiness without changing posting-content trust or stage success; raw browser/resolver errors are not exposed.

Employer-analysis requirements on job detail expose coverage_scope as resume, eligibility, logistics, employer_condition, or null for historical analyses. Both projection builders normalize missing historical values to null, so the TypeScript and Python read models remain identical.

Internal list/detail identity is the tenant-scoped stable JobId; posting URLs are locators resolved only at explicit import or API boundaries. Employer and Source remain independent facts. GET /v1/scoring/keywords aggregates indexed, normalized keywords from the current score version, and GET /v1/jobs accepts an exact normalizedScoreKeyword filter using those returned keys.

Interview Catalog, Preparation, And Notes ​

The global catalog/detail routes serve the same versioned public JSON packaged for Python and TypeScript. They remain available without a database, selected job, live worker, or provider call. The job action extends the existing explicit generate_interview_prep workflow with a bounded selected-question/context request. Prep history and independent revisioned notes use authorized job-scoped reads/saves. A failed refresh preserves accepted prep; a stale note revision returns an explicit conflict instead of overwriting the newer edit.

These contracts keep public guidance, accepted personal evidence, generated outlines, and unverified user edits separate. Read the Jobs & Materials route guide and complete interview contract for route fields, limits, errors, legacy cases, and stale-input semantics.

Feedback Learning And Policy History ​

GET /v1/learning/recommendations and its evidence route expose only bounded, privacy-safe derived evidence. The review route requires an explicit accepted or rejected decision. Acceptance creates a versioned Materials policy revision; rejection changes no policy. GET /v1/learning/policies/materials exposes the allowlisted current/superseded history, while the rollback route appends a new user-requested revision only against the reviewed current version and returns the recorded receipt on a matching retry, even after later changes. These routes never start scoring, tailoring, Apply, or artifact work. See Jobs & Materials.

Dashboard, Analytics, And Operational Metrics ​

GET /v1/dashboard/summary, GET /v1/analytics/outcomes, GET /v1/digest, and GET /v1/debug/activity expose read-side summaries. Their exact filters and shapes live in the complete contract.

GET /v1/pipeline/operations is the dedicated current-operations read model. It selects the active or draining Discover execution, falling back to the latest terminal execution, and combines immutable execution membership, pipeline-step and per-job stage projections, fresh worker capacity, approximate Temporal task-queue statistics, bounded active-work detail, and a conservative typed ETA. It is a current snapshot, not a historical reconstruction endpoint. See Operations & Events.

Discovery Controls ​

Source registry, quarantine, locator candidates, manual capture, schedules, and role-match feedback are covered in Profile & Settings. Source-locator GET uses a read-only database connection and never promotes. History proposals join saved source-control receipts and registry overlaps; review writes and durable coordinate decisions commit atomically. The history scan and explicit suppression reset are CLI-owned.

POST /v1/jobs/import-url accepts { "url": "https://…" } and awaits the worker-owned JobUrlImportWorkflow. It returns either the canonical imported jobKey or a typed manual_capture_required outcome with the pending queue item and reason. The API never fetches the remote page itself.

Job-detail Enrich stages expose optional fetchFailure diagnostics (cause, host, observed time, bounded recheck status/count/times). The audit history includes EnrichmentFetchRechecked; raw request URLs remain outside this diagnostic DTO. See fetch diagnostics.

Live Discovery Browser Bridge ​

GET /v1/discovery/browser-extension/status returns the memory-only bridge snapshot: connected, installationBound, the non-secret installationIdSuffix, lastSeenAt, extensionVersion, pendingTasks, and activeTasks. A heartbeat is current for 45 seconds. A pairing token can exist while connected is false; token presence is not readiness. POST /v1/extension/discovery/claim binds one extension-local installation UUID. The automatic background claim cannot establish or replace that selection; saving the token in the extension sends the explicit replacement claim.

The paired extension and local worker share a bounded task protocol under /v1/extension/discovery/tasks:

  • GET /v1/extension/discovery/tasks/next?extensionVersion=… heartbeats and leases the next task to the authenticated, selected extension installation;
  • GET /v1/extension/discovery/tasks/:taskId/lease?leaseId=… refreshes the selected installation heartbeat and tells the extension whether the exact lease remains active, allowing prompt worker cancellation;
  • POST /v1/extension/discovery/tasks lets only a token-authenticated local non-browser worker create an execution-bound task;
  • GET /v1/extension/discovery/tasks/:taskId lets that worker poll the result;
  • POST /v1/extension/discovery/tasks/:taskId/result accepts the extension's exact lease result after final-URL validation; and
  • DELETE /v1/extension/discovery/tasks/:taskId lets the worker cancel/forget its task.

Task schemas allow public HTTP(S) GET/POST or rendered-page acquisition, cap request bodies at 2 MB of UTF-8 data and response text/HTML at 4 MB of UTF-8 data each, cap timeouts at 120 seconds, and forbid browser-owned cookie/user-agent/origin plus Sec-*/Proxy-* headers. The broker is process-memory-only, idempotently binds a task ID to its workflow/run request fingerprint, separates a 30-second pending/admission window from the execution timeout that starts on lease, and admits at most four active/pending tasks for the extension's four executors. Active lease checks keep the 45-second bridge heartbeat current. Cancellation deletes the lease; the next control check aborts Chrome work and closes a tab when one exists. The API validates DNS at creation and again immediately before lease. HTTP/API tasks execute in the extension service worker with redirects disabled; rendered-page tasks use tab-scoped DNR rules that allow the exact source origin and block cross-origin main-frame redirects before dispatch without restricting page-owned fetch/XHR. Blocked cross-origin rendered navigation returns non-retryable unsafe_redirect promptly via a task-scoped navigation-error listener. Response streaming stops at its byte limit rather than buffering an unbounded body. Rotating the pairing token clears the selected installation and immediately marks the old extension disconnected. POST /v1/pipeline/actions/run-stage and the job-level and bulk Enrich run/retry routes do not require an extension heartbeat: worker-ready, eligible requests dispatch with the extension offline and return the normal 202 queued/accepted response, while worker readiness (503 worker_runtime_unavailable), stage eligibility, and authorization gates still apply. Each acquisition setup then prefers the connected extension after a bounded status probe and otherwise selects guarded public HTTP or anonymous Playwright; a fetch failure never switches transport. Top-level enrich, including mixed-stage requests containing it, is rejected with 400 by the request schema; Discovery owns the pipeline enrichment drain.

Compensation ​

Posted compensation, market estimates, source controls, and refresh actions are job evidence. Start with Jobs & Materials.

Workflow Runs ​

List, detail, cancel, and workflow-start semantics are in Operations & Events.

GET /v1/workflow-runs accepts the normal pagination and sort parameters plus an exact workflowType match, inclusive startedSince, and exclusive startedBefore. Timestamp bounds must be UTC ISO-8601 timestamps; malformed optional filter values are ignored. The list response echoes the effective filters in its filter object (null when an optional filter is inactive).

GET /v1/workflow-runs/:runId returns failureDiagnostics when available. automaticRetryable describes Temporal retry behavior; manualRecoveryAvailable describes whether the canonical Tailor stage is failed/retryable or exhausted for a failed JobPreparation Tailor run, so a new recovery can be started. Its optional providerFailures summary is limited to safe provider/model/operation failure dimensions, counts, and the latest correlated attempt; it excludes all prompt, generated-content, SDK-message, path, URL, and secret data. For matching pre-instrumentation Codex records only, the exact legacy parse_error: "builder error" shape is returned with source: "legacy_inferred"; per-call retryability and structured provider fields remain unavailable.

Discover, job preparation, and Apply share the same statuses, lifecycle timeline, terminal-state rules, and cancellation command. Cancellation is cooperative and idempotent: repeated requests do not replace terminal results or emit duplicate cancellation transitions.

Profile Resume Preview ​

GET /v1/profile/preview.html and GET /v1/profile/preview.pdf render the baseline profile resume. Per-job generated artifacts use the artifact preview routes described in Jobs & Materials.

Apply Review And Outcomes ​

Apply review is a user decision boundary, not a background side effect. The review queue, editable resume-review drafts, approval decisions, outcomes, and bounded Gmail suggestion flow are summarized in Jobs & Materials. Gmail scan summaries and feedback events carry canonical jobId values; the wire fields are defined in the complete contract.

Manual outcomes, interview reflections and user-attested send logs use a client-provided request identity to return the original result after a retry. See the manual action retry contract.

The queue and job-detail read models include repeatApplication: its current status, summary, evaluation time, evidence fingerprint, related confirmed prior applications, matching reason and identity evidence, matching one-attempt override, and bounded audit trail. POST /v1/jobs/:jobKey/repeat-application/override accepts evidenceFingerprint, priorJobKey, a 10–400 character reason, and confirmedBy; it records intent but does not dispatch Apply. Stale evidence, prior mismatch, already-consumed confirmation, and live apply without required confirmation return 409 with stable repeat-application error codes. Dry-run dispatch does not require this confirmation because it cannot submit.

Contacts ​

Contact facts, reviewed CSV/vCard import, supervised research, candidate confirmation, outreach drafts, send logs, and follow-ups share one route family. See Jobs & Materials. Research source attempts distinguish acquisition failures from a successful page with no candidates; their detailed outcomes and recovery text are returned on the research task detail read.

Pipeline And Preparation Actions ​

Global and per-job stage runs, rescore/re-tailor, retry, mark-applied, and mark-skipped commands are summarized in Operations & Events. Enrichment resets use the synchronous Python application service through reset_enrichment; its transaction and preservation rules are defined in Enrichment Retry Ownership.

Discovery reads the saved profile target, location, and work-model preferences. The precedence and filter details remain in the complete contract.

Worker Runtime And Health ​

Use GET /v1/health before starting work that needs Temporal and the Python worker. See Operations & Events for the readiness boundary.

Settings And Credentials ​

Ordinary settings and secrets deliberately use separate routes and storage. See Profile & Settings. GET /v1/providers/models returns sanitized ready-provider catalogs through the Python JSON-RPC boundary. PATCH /v1/settings accepts preferredModels, validates every non-null choice against that current catalog, and persists only the canonical preferred_models provider/model mapping; null clears one provider without requiring it to be ready. The saved defaultLlmModel route and explicit material generator and judge choices use the same catalog boundary before any settings bytes change. Clearing them or selecting provider-neutral default does not require provider availability. defaultLlmModel persists as default_llm_model; it binds newly acquired core adapters and the optional writing pass without changing explicit workflow pins. Codex catalog models also expose optional supportedReasoningEfforts and defaultReasoningEffort capabilities. Settings accepts model-specific codexReasoningEfforts patches validated against those live options; null entries clear offline. The optional reasoning_effort in determination envelopes records an explicit saved choice and participates in the determination identity. Legacy envelopes without the field remain readable.

GET/PATCH /v1/settings also carries effective-source and activation metadata for launch controls. Environment-owned fields are read-only. Discovery runtime and schedule controls use GET/PATCH /v1/discovery/settings; Apply-browser adoption uses GET /v1/browser-capabilities plus capability-specific POST enable and disable routes. The older profile-copy route remains API-compatible for existing installations but is not exposed by current Settings and does not start LinkedIn recovery. Extension pairing uses GET /v1/extension/pairing-token and POST /v1/extension/pairing-token/rotate; live current-profile readiness uses GET /v1/discovery/browser-extension/status. Integrated Discovery and Enrich never read an adopted executable or copied profile.

The contracts package owns shared API types, the API-client package owns typed browser calls, and apps/api owns HTTP/JSON-RPC/SSE transport. Source pointers are collected in Operations & Events.

Commands ​

Use corepack pnpm api:dev for the API, corepack pnpm web:dev for the web app, and corepack pnpm dev for the complete attached local stack. The full setup and verification sequence is in Local Development.

Server-Sent Events — GET /v1/events/stream ​

The endpoint tails tenant-scoped job_events, emits typed SSE frames, and uses event IDs for replay. If the event database cannot be opened or read, the stream signals that it is unavailable and retains each subscriber's cursor. It retries without terminating the API; a detected database change sends a cursor-reset signal and stays degraded until stopped-runtime recovery and a fresh UI subscription. The browser shows the recovery steps. Path checks cannot identify an A → B → A replacement during initial SQLite open; stop JobCtrl before any database restore or replacement. The invalidation router maps each event type to the smallest safe query-key set. See Operations & Events and the frontend realtime architecture.

Internal ArchivedSmartPostingPrepared receipts retain private captured posting bytes for recovery and are excluded entirely from SSE. Capture completion and source-owner notifications contain references only; they do not expose that private journal body.

Request ​

The browser opens one long-lived GET request. A reconnect may include Last-Event-ID; the server resumes after that event when it is still available.

Response Framing ​

Frames include an id, typed event, JSON data, and a reconnect cadence. DryRunCompleted is a distinct typed apply event: it refreshes dry-run status without implying submission. Its payload contract retains the launcher's snake_case lifecycle fields. Clients validate the payload before dispatching it to the invalidation router.

Tenant Filtering (COALESCE On The Row, Not The Request) ​

Tenant compatibility is resolved against each stored row. The request tenant is never broadened to turn a tenant-scoped stream into an unscoped one.

Resume-Position Precedence ​

Resume and replay positions follow the explicit request/header contract before falling back to the current tail. See the complete contract for the exact order.

Cadences ​

The server polls for durable events and sends keepalive traffic on separate, bounded cadences so quiet connections remain observable.

Reconnect Backstop ​

Native EventSource reconnect plus durable event IDs is the normal recovery path. Projection refetches remain the correctness backstop if a client misses an invalidation.

Workflow-run detail also returns the selected Apply run's durable event summaries separately from workflow lifecycle events. Their identities and privacy boundary are documented in Operations & Events.

Required-Bullet Coaching ​

The opt-in POST /v1/profile/required-bullet-suggestions route calls the configured LLM through the synchronous profile_required_bullet_suggestions worker RPC. Findings bind to canonical saved sources and the expected profile version; provider failures have no heuristic fallback. Typed budget denials return 429 with daily/profile-lane scope; provider setup/authentication failures return 503; invalid output or other provider failures return 502 with safe actionable messages. This route uses observed daily/profile-lane admission, not a hard per-call token or cost ceiling; input/response bounds do not cap an in-flight provider charge. Only an exact model-proposed whitespace cleanup can be accepted through the ordinary fenced profile save. See Profile & Settings and the complete contract for bounds and errors.

Semantic Determination Authority ​

Semantic read envelopes and citations are shared in Contracts. Discovery executes saved provider searches and ingests structurally valid fetched listings without a separate intake gate; /v1/extension/autofill/mapping uses paired sync RPC; saved resume rendering requires model review of that exact revision. Target-role suggestions are model-backed pending determinations. Version-fenced profile saves may carry acceptedCandidateInterpretationId to record confirmation. Repeat checks persist equivalence before live Apply eligibility is read.

See the decision for caching, provenance and failure contracts.

Private screening-answer reads and commands use the generated endpoint clients and registered screening_answers Python RPC. See Jobs and Materials.

The existing Job Detail and daily digest reads also expose optional saved company briefings. They perform no fetch/model work; configuration and refresh are explicit local CLI commands. See the wire contract.

Provider Endpoint Registration ​

GET /v1/providers/status, GET /v1/providers/models, and POST /v1/providers/codex/verify share the existing endpoint specification, registered feature handlers and generated checked clients. Legacy query/body values remain ignored. The provider contract defines status/error compatibility; the migration recipe shows how to adopt later families without changing the #954 envelope boundary.

Accepted DOCX Export ​

Job Detail exports original accepted resume and cover-letter text through the strict material-exports endpoint and runtime-bound Python RPC. Registered DOCX downloads verify owned paths and output hashes. See Jobs & Materials and the complete contract for request fields, source fences and errors.

Actual offer terms and accepted/declined decisions use the private per-job /v1/jobs/:jobId/offer route family; Jobs & Materials API → Actual Offers owns its source, expected-revision and correction semantics. Offer communication uses the existing reviewed outreach routes and manual-send boundary.