{"openapi":"3.1.0","info":{"title":"maxcv.ai API","version":"0.1.0","description":"Tailor a CV/resume to a specific job posting. Rules and automated checks designed to prevent added facts, ATS keyword optimization, match score and interview prep. /api/score and /api/cv/analyze are open to agents without login or Turnstile (free trial tier, no API key), gated by per-IP rate limits and a global daily cap (HTTP 429 when exceeded). See also the MCP server at /mcp and the agent skill at /maxcv/SKILL.md.","contact":{"email":"support@coderai.dev","url":"https://maxcv.ai"}},"servers":[{"url":"https://maxcv.ai"}],"paths":{"/api/score":{"post":{"summary":"Score how well a CV matches a job posting (score-only).","description":"Returns one source-CV score (assessmentKind=source_only, method=jev-probability-v1) with per-requirement evidence probabilities. originalScore and matchScore are equal compatibility aliases for this source score, never a predicted after score. Open to agents with no login, no Turnstile and no API key (free trial tier); gated by per-IP rate limits plus a global daily cap. Returns HTTP 429 when a cap is exceeded — back off and direct the user to sign up.","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"cvText":{"type":"string","description":"CV as plain text (or upload `cv` as a file instead)."},"cv":{"type":"string","format":"binary","description":"CV as a PDF or Word (.docx) file. A .docx is read locally; a PDF goes whole, contact details included, through OpenRouter to the model that reads its text, before masking, with zero data retention routing required (a routing requirement, not a guarantee)."},"jobDescription":{"type":"string","description":"The job posting text."}},"required":["jobDescription"]}}}},"responses":{"200":{"description":"Score result","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"originalScore":{"type":"number","description":"Source CV score; compatibility alias of assessment.score."},"matchScore":{"type":"number","description":"Same source score; no generated-after estimate."},"assessmentKind":{"type":"string","enum":["source_only"]},"method":{"type":"string","enum":["jev-probability-v1"]},"assessment":{"type":"object","description":"Source assessment: score=round(100*mean(probabilityMet)); all requirements retain equal weight. Includes actual model version, requirementCount, matchedCount and unknownCount.","properties":{"score":{"type":"number"},"model":{"type":["string","null"]},"requirements":{"$ref":"#/components/schemas/ProbabilityRequirements"},"requirementCount":{"type":"integer"},"matchedCount":{"type":"integer"},"unknownCount":{"type":"integer"}}},"requirementCount":{"type":"integer"},"matchedCount":{"type":"integer"},"requirements":{"$ref":"#/components/schemas/ProbabilityRequirements"},"excludedFromScoreCount":{"type":"integer","enum":[0],"description":"No unknown rows are excluded."}}}}}}}},"413":{"description":"Scoring context exceeds limits: each CV/offer at most 20,000 characters; combined serialized CV+offer JSON at most 20,000 UTF-8 bytes. Inputs are rejected before billed pipeline processing."},"429":{"description":"Rate limit exceeded"}}}},"/api/tailor":{"post":{"summary":"Tailor a CV to a job posting (full pipeline).","description":"Generates a tailored CV with ATS keywords, actual before/after probability scores, role-fit assessment, interview prep and, for outputFormat=json, a draft cover letter (data.coverLetter, null when unavailable). data.requirementAssessment contains before and after assessments against the same frozen requirement list and method=jev-probability-v1. The actual after score may be lower than originalScore; no gain is guaranteed. Requires authentication (credits). For a no-login trial use the MCP server at /mcp.","security":[{"sessionCookie":[]}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"cvText":{"type":"string"},"cv":{"type":"string","format":"binary"},"jobDescription":{"type":"string"},"outputFormat":{"type":"string","enum":["json","html","pdf"],"default":"json"},"skipCoverLetter":{"type":"boolean","default":false,"description":"Skip the draft cover letter. A letter is generated only for outputFormat=json."},"style":{"type":"string","description":"Optional JSON: {\"preset\":\"modern|classic|compact|sidebar|executive|bold|user\",\"overrides\":{\"accent\":\"#RRGGBB\",\"font\":\"inter\",\"density\":\"normal\"}}. The user selector reads stored detected tokens from the signed-in account. Applies to HTML/PDF output; otherwise the saved account preference or detected style or classic is used."}},"required":["jobDescription"]}}}},"responses":{"200":{"description":"Tailored CV (JSON, HTML or PDF depending on outputFormat).","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"originalScore":{"type":"number","description":"Actual source assessment score."},"tailoredCV":{"type":"object","properties":{"matchScore":{"type":"number","description":"Actual final-CV score, which may decrease."}}},"requirementAssessment":{"type":"object","required":["before","after","method"],"properties":{"method":{"type":"string","enum":["jev-probability-v1"]},"before":{"$ref":"#/components/schemas/ProbabilityAssessment"},"after":{"$ref":"#/components/schemas/ProbabilityAssessment"}}}}}}}}}},"402":{"description":"Insufficient credits"},"413":{"description":"Source scoring context exceeds limits (20,000 characters per text, 20,000 combined serialized UTF-8 bytes). An oversized generated final CV also fails rather than being silently truncated."}}}},"/api/render/docx":{"post":{"summary":"Render an existing tailored CV as an editable, one-column Word file.","description":"Generated in-process with its own daily limit (see https://maxcv.ai/terms#free-paid). JSON body up to 256 KB. The output is a plain ATS-friendly .docx; template styles do not apply and any style field is ignored.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["tailoredCV"],"properties":{"tailoredCV":{"type":"object"},"contactInfo":{"type":"object","description":"Partial contact details for the header. A malformed value never blocks the download."},"meta":{"type":"object","description":"Filename hints only.","properties":{"company":{"type":"string"},"title":{"type":"string"}}}}}}}},"responses":{"200":{"description":"Word (.docx) file named Company-Role-Name.docx."},"400":{"description":"Invalid JSON or tailoredCV."},"409":{"description":"The signed-in session changed since the result was created."},"413":{"description":"Body larger than 256 KB."},"429":{"description":"Daily limit exceeded (see https://maxcv.ai/terms#free-paid)."}}}},"/api/render/pdf":{"post":{"summary":"Render an existing tailored CV or cover letter as PDF.","description":"tailoredCV is required unless kind is coverLetter, which renders coverLetter.text with the signature added from contactInfo. Accepts an optional validated CV template selector for signed-in users, including user to select their stored detected style. Guests can render classic only; without a style, the saved account preference, detected style or classic applies.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"kind":{"type":"string","enum":["coverLetter"],"description":"Omit to render tailoredCV."},"tailoredCV":{"type":"object","description":"Required unless kind is coverLetter."},"contactInfo":{"type":"object"},"coverLetter":{"type":"object","description":"Required with kind coverLetter.","required":["text"],"properties":{"text":{"type":"string","maxLength":6000},"language":{"type":"string","maxLength":12}}},"style":{"type":"object","properties":{"preset":{"type":"string","enum":["classic","modern","compact","sidebar","executive","bold","user"],"description":"user selects the detected style stored on the signed-in account; client style tokens are ignored."},"overrides":{"type":"object","properties":{"accent":{"type":"string","pattern":"^#[0-9A-Fa-f]{6}$"},"font":{"type":"string","enum":["system","inter","source-sans-3","lato","ibm-plex-sans","source-serif-4","eb-garamond","carlito","caladea","arimo","tinos","gelasio","roboto","open-sans","montserrat","raleway","ibm-plex-mono"]},"density":{"type":"string","enum":["compact","normal","airy"]}}}},"required":["preset"]}}}}}},"responses":{"200":{"description":"PDF file."},"400":{"description":"Invalid CV, cover letter or style."},"401":{"description":"Template selection requires sign-in."}}}},"/api/matches/{id}/cover-letter":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"summary":"Get or generate the draft cover letter of a saved match.","description":"Returns the stored letter, or generates one when none is stored or regenerate is true. No credit cost; generations have daily limits (see https://maxcv.ai/terms#free-paid). The draft is based only on the source CV and the job data stored with the match; read it before sending.","security":[{"sessionCookie":[]}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"regenerate":{"type":"boolean"}}}}}},"responses":{"200":{"description":"{ coverLetter: { text, language, generatedAt, edited?, generatedText? }, generated: boolean, persisted: boolean }"},"401":{"description":"Authentication required."},"404":{"description":"Match not found."},"422":{"description":"The match has no data to write a letter from."},"429":{"description":"Daily cover letter cap reached."},"502":{"description":"The letter could not be generated."},"503":{"description":"Temporarily unavailable."}}},"put":{"summary":"Save a user edit of an existing cover letter.","description":"Updates only the letter of the match and never calls a model.","security":[{"sessionCookie":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["text"],"properties":{"text":{"type":"string","maxLength":6000},"generatedText":{"type":"string","maxLength":6000,"description":"The model version, kept once for reset."}}}}}},"responses":{"200":{"description":"{ savedAt, persisted: boolean }"},"400":{"description":"Invalid cover letter."},"401":{"description":"Authentication required."},"404":{"description":"Match or letter not found."},"429":{"description":"Rate limit exceeded."},"503":{"description":"Temporarily unavailable."}}}},"/api/account/cv-template":{"get":{"summary":"Get the signed-in user’s CV template preference and detected style.","security":[{"sessionCookie":[]}],"responses":{"200":{"description":"Preference, detectedStyle, and opaque cvRevision (or null)."}}},"put":{"summary":"Save the signed-in user’s CV template preference.","security":[{"sessionCookie":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["preference"],"properties":{"preference":{"type":"object","description":"Either {mode:\"auto\"} or {mode:\"preset\",preset:\"classic|modern|compact|sidebar|executive|bold\",overrides?}."}}}}}},"responses":{"200":{"description":"Saved preference."},"400":{"description":"Invalid preference."}}}},"/api/account/cv-style":{"put":{"summary":"Save detected CV style tokens for the current master CV.","security":[{"sessionCookie":[]}],"description":"The revision returned by /api/cv/analyze or GET /api/account/cv-template must match the stored CV. Only a validated bounded style with preset user, or null, is accepted.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["style","revision"],"properties":{"style":{"oneOf":[{"type":"object","description":"CvStyle tokens with preset user."},{"type":"null"}]},"revision":{"type":"string","pattern":"^[a-f0-9]{64}$"}}}}}},"responses":{"200":{"description":"Saved detected style."},"400":{"description":"Invalid style or revision."},"409":{"description":"CV revision is stale."}}}},"/api/cv/analyze":{"post":{"summary":"Parse a CV into a structured object.","description":"Parses a CV (plain text, PDF or Word .docx) into structured JSON. Open to agents with no login, no Turnstile and no API key (free trial tier); gated by per-IP rate limits plus a global daily cap. Returns HTTP 429 when a cap is exceeded.","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"cvText":{"type":"string"},"cv":{"type":"string","format":"binary"}}}}}},"responses":{"200":{"description":"Structured CV"},"429":{"description":"Rate limit exceeded"}}}}},"components":{"securitySchemes":{"sessionCookie":{"type":"apiKey","in":"cookie","name":"session"}},"schemas":{"ProbabilityRequirements":{"type":"array","items":{"type":"object","required":["id","text","category","priority","probabilityMet","probabilityNotMet","probabilityUnknown","confidence","status","countedInScore"],"properties":{"id":{"type":"string"},"text":{"type":"string"},"category":{"type":"string"},"priority":{"type":"string"},"probabilityMet":{"type":"number","minimum":0,"maximum":1},"probabilityNotMet":{"type":"number","minimum":0,"maximum":1},"probabilityUnknown":{"type":"number","minimum":0,"maximum":1},"confidence":{"type":"number","minimum":0,"maximum":1},"status":{"type":"string","enum":["met","not_met","unknown"]},"countedInScore":{"type":"boolean","enum":[true]}}}},"ProbabilityAssessment":{"type":"object","required":["score","requirements","model","requirementCount","matchedCount","unknownCount"],"properties":{"score":{"type":"number","description":"round(100 * mean(probabilityMet)); all frozen features remain in the denominator."},"requirements":{"$ref":"#/components/schemas/ProbabilityRequirements"},"model":{"type":["string","null"]},"requirementCount":{"type":"integer"},"matchedCount":{"type":"integer"},"unknownCount":{"type":"integer"}}}}}}