CODEX // oaoisme wiki

§ unswayed-backend · API contract & docs

Lexi AI — cover letters (Phase 14, UN-111)

updated 2026-06-17

What it is

The cover-letters slice (src/lexi-ai/cover-letters/) lets an applicant generate a cover letter for a specific job, in a chosen tone, then keep/edit/delete their letters. Part of the per-user Lexi /api/v1/* career suite (applicant-only, bare camelCase, per-user rate limits).

Endpoints

  • POST /api/v1/users/{userId}/cover-letters/generate — 15/hour, behind the coverLetterGeneration feature flag. Body {resumeId, jobPostingId | jobDescriptionText, tone?}. The LLM is grounded in the applicant's parsed resume (ApplicantResume.resumeData) + the JD (a platform job or pasted text) + the chosen tone (formal default | conversational | enthusiastic | concise). Each call creates a new CoverLetter row (source:ai_generated) — regenerating with a different tone never overwrites a prior version. Returns the letter plus highlightedPoints — a flat list of the resume↔JD connections the letter leaned on, so the user can verify accuracy. 201.
  • GET /api/v1/users/{userId}/cover-letters?jobPostingId= — paginated list, filterable by job.
  • GET /api/v1/users/{userId}/cover-letters/{id} — detail (404 if not owned).
  • PUT /api/v1/users/{userId}/cover-letters/{id} — manual edit ({content, tone?}); flips sourceedited. Editing one letter never touches another.
  • DELETE /api/v1/users/{userId}/cover-letters/{id} — 204.

How it works

The slice reuses the shared AI seam (AiCompletionPort.complete(..., {model: models.lenux, responseFormat:'json'}), gpt-4o-mini, retry-once parser; LexiUnavailableError → 503) and the per-user ownership/feature primitives (LexiUserContextService, LexiFeatureGuard). Generation rejects a resume with no parsed content (422). CoverLetterService.generate(...) is exported so the Lexi chat slice (UN-114) can call it from a confirmed generate_cover_letter action.

The highlightedPoints mirror the "reasonOfMatch" idea from the scoring engine — a small, plain-language trust signal rather than an opaque blob.