{"openapi":"3.1.0","info":{"title":"Global Eyes API","version":"0.1.0","description":"Backend for the Global Eyes frontend.\n\nEvery route below `/api/auth` requires authentication. The browser flow uses\nhttpOnly cookies set by `/api/auth/login`; for `curl` and the \"Try it out\"\nbutton here, an `Authorization: Bearer <accessToken>` header also works.\n\nDates come in two forms and neither carries a timezone: calendar dates are\n`YYYY-MM-DD`, and interview times are `YYYY-MM-DDTHH:mm:ss` local wall-clock.\n`createdAt` / `updatedAt` are true instants in full ISO-8601 with `Z`."},"servers":[{"url":"/","description":"This server"}],"tags":[{"name":"Auth","description":"Registration, sessions and token rotation."},{"name":"Bids","description":"The one first-class entity: a job application."},{"name":"Profile","description":"The single résumé profile, global per user."},{"name":"Settings","description":"Daily bid goal and theme."},{"name":"Dashboard","description":"Server-side aggregates for the Dashboard screen."},{"name":"Data","description":"Whole-account export, import and reset."},{"name":"Jobs","description":"Reading a job posting from its public URL."},{"name":"Resume","description":"Writing a résumé for one application with an AI platform."},{"name":"Cover letter","description":"The step after the résumé: a letter written from it and from the same posting, laid out on the same page."},{"name":"Questions","description":"The conversation about one application: the extra questions an employer's form asks beside the résumé, answered in the applicant's voice."},{"name":"Admin","description":"Administrator-only: managing accounts and their profiles, and the deployment-wide AI settings. Every route here answers 403 for a plain account."}],"components":{"schemas":{"Register":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"email":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"password":{"type":"string","minLength":12,"maxLength":200},"firstName":{"type":"string","minLength":1,"maxLength":100},"lastName":{"type":"string","minLength":1,"maxLength":100},"dateOfBirth":{"default":"","type":"string","maxLength":10}},"required":["email","password","firstName","lastName"]},"LoginRequest":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"email":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"password":{"type":"string","minLength":1}},"required":["email","password"]},"AuthUser":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"role":{"type":"string","enum":["user","admin"]},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"}},"required":["id","email","role","createdAt"],"additionalProperties":false},"Bid":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"candidate":{"description":"Who the application is filed under. Always the caller for a plain account; any profile in an admin's list.","type":"object","properties":{"userId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"firstName":{"type":"string"},"lastName":{"type":"string"},"displayName":{"type":"string"},"photoUrl":{"type":"string","format":"uri"}},"required":["userId","firstName","lastName","displayName"],"additionalProperties":false},"company":{"type":"string"},"role":{"type":"string"},"jobUrl":{"type":"string"},"hasResume":{"type":"boolean"},"hasCoverLetter":{"type":"boolean"},"chatMessageCount":{"description":"How many turns the conversation about this application ran to. The transcript is at GET /api/bids/{id}/chat.","type":"number"},"location":{"type":"string"},"jobDescription":{"type":"string"},"rateType":{"type":"string","enum":["hourly","annually"]},"rateAmount":{"type":"number"},"bidDate":{"type":"string"},"status":{"type":"string","enum":["Draft","Applied","Screening (Round 1)","Interview (Round 2)","Interview (Round 3)","Final Interview","Offer","Background Check Passed","Started","Completed","Failed"]},"followUpDate":{"type":"string"},"resumeVersion":{"type":"string"},"notes":{"type":"string"},"commitmentDetails":{"type":"object","properties":{"startDate":{"type":"string"},"endDate":{"type":"string"},"contractType":{"type":"string","minLength":1,"maxLength":100},"agreedRate":{"type":"number","minimum":0,"maximum":10000000}},"required":["startDate","contractType","agreedRate"],"additionalProperties":false},"interviews":{"type":"array","items":{"type":"object","properties":{"stage":{"type":"string","enum":["Screening (Round 1)","Interview (Round 2)","Interview (Round 3)","Final Interview"]},"dateTime":{"type":"string"}},"required":["stage","dateTime"],"additionalProperties":false}},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"updatedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"}},"required":["id","company","role","location","bidDate","status","createdAt","updatedAt"],"additionalProperties":false},"CreateBid":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"company":{"type":"string","minLength":1,"maxLength":200},"role":{"type":"string","minLength":1,"maxLength":200},"jobUrl":{"type":"string","maxLength":2048,"format":"uri"},"location":{"default":"","type":"string","maxLength":200},"jobDescription":{"type":"string","maxLength":50000},"rateType":{"type":"string","enum":["hourly","annually"]},"rateAmount":{"type":"number","minimum":0,"maximum":10000000},"bidDate":{"type":"string"},"status":{"default":"Draft","type":"string","enum":["Draft","Applied","Screening (Round 1)","Interview (Round 2)","Interview (Round 3)","Final Interview","Offer","Background Check Passed","Started","Completed","Failed"]},"followUpDate":{"type":"string"},"resumeVersion":{"type":"string","maxLength":200},"notes":{"type":"string","maxLength":20000},"interviews":{"maxItems":4,"type":"array","items":{"type":"object","properties":{"stage":{"type":"string","enum":["Screening (Round 1)","Interview (Round 2)","Interview (Round 3)","Final Interview"]},"dateTime":{"type":"string"}},"required":["stage","dateTime"]}},"commitmentDetails":{"type":"object","properties":{"startDate":{"type":"string"},"endDate":{"type":"string"},"contractType":{"type":"string","minLength":1,"maxLength":100},"agreedRate":{"type":"number","minimum":0,"maximum":10000000}},"required":["startDate","contractType","agreedRate"]},"resumeDocument":{"anyOf":[{"type":"object","properties":{"company":{"type":"string","description":"The company applied to, echoed back."},"role":{"type":"string","description":"The role applied for, echoed back."},"roleTitles":{"type":"array","items":{"type":"string"},"description":"One title per employer, newest first, aligned with `bullets`."},"summary":{"type":"string","description":"One continuous paragraph. No newlines, no bullets."},"skills":{"type":"array","items":{"type":"string"},"description":"One line per category, e.g. \"Languages: Go | Python | Java | TypeScript\"."},"bullets":{"type":"array","items":{"type":"array","items":{"type":"string"}},"description":"One group per employer, newest first, aligned with `roleTitles`."}},"required":["company","role","roleTitles","summary","skills","bullets"]},{"type":"null"}]},"coverLetterDocument":{"anyOf":[{"type":"object","properties":{"company":{"type":"string","description":"The company applied to, echoed back."},"role":{"type":"string","description":"The role applied for, echoed back."},"greeting":{"type":"string","description":"The salutation, e.g. \"Dear Hiring Manager,\"."},"paragraphs":{"type":"array","items":{"type":"string"},"description":"The body, one continuous paragraph per entry: the Hook, the Core Value Proposition, the Remote Proof and the Call to Action. `**bold**` is the only markup allowed inside them."},"closing":{"type":"string","description":"The sign-off above the name, e.g. \"Sincerely,\"."}},"required":["company","role","greeting","paragraphs","closing"]},{"type":"null"}]},"applicationChat":{"anyOf":[{"maxItems":100,"type":"array","items":{"type":"object","properties":{"role":{"type":"string","enum":["user","assistant"]},"content":{"type":"string","minLength":1,"maxLength":20000}},"required":["role","content"]}},{"type":"null"}]},"userId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["company","role","bidDate"]},"UpdateBid":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"company":{"type":"string","minLength":1,"maxLength":200},"role":{"type":"string","minLength":1,"maxLength":200},"jobUrl":{"type":"string","maxLength":2048,"format":"uri"},"location":{"type":"string","maxLength":200},"jobDescription":{"type":"string","maxLength":50000},"rateType":{"type":"string","enum":["hourly","annually"]},"rateAmount":{"type":"number","minimum":0,"maximum":10000000},"bidDate":{"type":"string"},"status":{"type":"string","enum":["Draft","Applied","Screening (Round 1)","Interview (Round 2)","Interview (Round 3)","Final Interview","Offer","Background Check Passed","Started","Completed","Failed"]},"followUpDate":{"type":"string"},"resumeVersion":{"type":"string","maxLength":200},"notes":{"type":"string","maxLength":20000},"interviews":{"maxItems":4,"type":"array","items":{"type":"object","properties":{"stage":{"type":"string","enum":["Screening (Round 1)","Interview (Round 2)","Interview (Round 3)","Final Interview"]},"dateTime":{"type":"string"}},"required":["stage","dateTime"]}},"commitmentDetails":{"anyOf":[{"type":"object","properties":{"startDate":{"type":"string"},"endDate":{"type":"string"},"contractType":{"type":"string","minLength":1,"maxLength":100},"agreedRate":{"type":"number","minimum":0,"maximum":10000000}},"required":["startDate","contractType","agreedRate"]},{"type":"null"}]},"resumeDocument":{"anyOf":[{"type":"object","properties":{"company":{"type":"string","description":"The company applied to, echoed back."},"role":{"type":"string","description":"The role applied for, echoed back."},"roleTitles":{"type":"array","items":{"type":"string"},"description":"One title per employer, newest first, aligned with `bullets`."},"summary":{"type":"string","description":"One continuous paragraph. No newlines, no bullets."},"skills":{"type":"array","items":{"type":"string"},"description":"One line per category, e.g. \"Languages: Go | Python | Java | TypeScript\"."},"bullets":{"type":"array","items":{"type":"array","items":{"type":"string"}},"description":"One group per employer, newest first, aligned with `roleTitles`."}},"required":["company","role","roleTitles","summary","skills","bullets"]},{"type":"null"}]},"coverLetterDocument":{"anyOf":[{"type":"object","properties":{"company":{"type":"string","description":"The company applied to, echoed back."},"role":{"type":"string","description":"The role applied for, echoed back."},"greeting":{"type":"string","description":"The salutation, e.g. \"Dear Hiring Manager,\"."},"paragraphs":{"type":"array","items":{"type":"string"},"description":"The body, one continuous paragraph per entry: the Hook, the Core Value Proposition, the Remote Proof and the Call to Action. `**bold**` is the only markup allowed inside them."},"closing":{"type":"string","description":"The sign-off above the name, e.g. \"Sincerely,\"."}},"required":["company","role","greeting","paragraphs","closing"]},{"type":"null"}]},"applicationChat":{"anyOf":[{"maxItems":100,"type":"array","items":{"type":"object","properties":{"role":{"type":"string","enum":["user","assistant"]},"content":{"type":"string","minLength":1,"maxLength":20000}},"required":["role","content"]}},{"type":"null"}]},"userId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}}},"BulkStatus":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"ids":{"minItems":1,"maxItems":500,"type":"array","items":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"status":{"type":"string","enum":["Draft","Applied","Screening (Round 1)","Interview (Round 2)","Interview (Round 3)","Final Interview","Offer","Background Check Passed","Started","Completed","Failed"]},"interviewDateTime":{"type":"string"}},"required":["ids","status"]},"BulkDelete":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"ids":{"minItems":1,"maxItems":500,"type":"array","items":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}}},"required":["ids"]},"Profile":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"firstName":{"type":"string"},"lastName":{"type":"string"},"dateOfBirth":{"type":"string"},"location":{"type":"string"},"email":{"type":"string"},"phone":{"type":"string"},"photoUrl":{"description":"A signed, short-lived link to the candidate photo. Absent when no photo has been uploaded, or when this deployment has no S3 bucket configured. Read-only: PUT ignores it.","type":"string","format":"uri"},"workExperience":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","maxLength":64},"company":{"default":"","type":"string","maxLength":200},"location":{"default":"","type":"string","maxLength":200},"startDate":{"default":""},"endDate":{"default":null}},"required":["id","company","location","startDate","endDate"],"additionalProperties":false}},"education":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","maxLength":64},"degree":{"default":"","type":"string","maxLength":200},"major":{"default":"","type":"string","maxLength":200},"university":{"default":"","type":"string","maxLength":200},"location":{"default":"","type":"string","maxLength":200},"startDate":{"default":""},"endDate":{"default":null}},"required":["id","degree","major","university","location","startDate","endDate"],"additionalProperties":false}}},"required":["firstName","lastName","dateOfBirth","location","email","phone","workExperience","education"],"additionalProperties":false},"ProfileInput":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"firstName":{"default":"","type":"string","maxLength":100},"lastName":{"default":"","type":"string","maxLength":100},"dateOfBirth":{"default":"","type":"string","maxLength":10},"location":{"default":"","type":"string","maxLength":200},"email":{"default":"","type":"string","maxLength":254},"phone":{"default":"","type":"string","maxLength":40},"workExperience":{"maxItems":100,"type":"array","items":{"type":"object","properties":{"id":{"type":"string","maxLength":64},"company":{"default":"","type":"string","maxLength":200},"location":{"default":"","type":"string","maxLength":200},"startDate":{"type":"string","maxLength":10},"endDate":{"type":["string","null"]}}}},"education":{"maxItems":100,"type":"array","items":{"type":"object","properties":{"id":{"type":"string","maxLength":64},"degree":{"default":"","type":"string","maxLength":200},"major":{"default":"","type":"string","maxLength":200},"university":{"default":"","type":"string","maxLength":200},"location":{"default":"","type":"string","maxLength":200},"startDate":{"type":"string","maxLength":10},"endDate":{"type":["string","null"]}}}}}},"AppSettings":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"dailyBidGoal":{"default":100,"type":"integer","minimum":0,"maximum":10000},"theme":{"default":"light","type":"string","enum":["light","dark"]}}},"Dashboard":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"totalApplications":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"statusBreakdown":{"type":"array","items":{"type":"object","properties":{"status":{"type":"string","enum":["Draft","Applied","Screening (Round 1)","Interview (Round 2)","Interview (Round 3)","Final Interview","Offer","Background Check Passed","Started","Completed","Failed"]},"count":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["status","count"],"additionalProperties":false}},"dailyGoal":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"todayCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"dailyVolume":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string"},"count":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["date","count"],"additionalProperties":false}},"activity":{"type":"object","properties":{"from":{"type":"string"},"to":{"type":"string"},"days":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string"},"count":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["date","count"],"additionalProperties":false}}},"required":["from","to","days"],"additionalProperties":false},"upcomingInterviews":{"type":"array","items":{"type":"object","properties":{"bidId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"company":{"type":"string"},"role":{"type":"string"},"stage":{"type":"string","enum":["Draft","Applied","Screening (Round 1)","Interview (Round 2)","Interview (Round 3)","Final Interview","Offer","Background Check Passed","Started","Completed","Failed"]},"dateTime":{"type":"string"}},"required":["bidId","company","role","stage","dateTime"],"additionalProperties":false}},"startedContracts":{"type":"array","items":{"type":"object","properties":{"bidId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"company":{"type":"string"},"role":{"type":"string"},"rate":{"type":["number","null"]},"rateType":{"anyOf":[{"type":"string","enum":["hourly","annually"]},{"type":"null"}]},"startedOn":{"type":"string"}},"required":["bidId","company","role","rate","rateType","startedOn"],"additionalProperties":false}}},"required":["totalApplications","statusBreakdown","dailyGoal","todayCount","dailyVolume","activity","upcomingInterviews","startedContracts"],"additionalProperties":false},"DataBundle":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"bids":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"candidate":{"description":"Who the application is filed under. Always the caller for a plain account; any profile in an admin's list.","type":"object","properties":{"userId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"firstName":{"type":"string"},"lastName":{"type":"string"},"displayName":{"type":"string"},"photoUrl":{"type":"string","format":"uri"}},"required":["userId","firstName","lastName","displayName"],"additionalProperties":false},"company":{"type":"string"},"role":{"type":"string"},"jobUrl":{"type":"string"},"hasResume":{"type":"boolean"},"hasCoverLetter":{"type":"boolean"},"chatMessageCount":{"description":"How many turns the conversation about this application ran to. The transcript is at GET /api/bids/{id}/chat.","type":"number"},"location":{"type":"string"},"jobDescription":{"type":"string"},"rateType":{"type":"string","enum":["hourly","annually"]},"rateAmount":{"type":"number"},"bidDate":{"type":"string"},"status":{"type":"string","enum":["Draft","Applied","Screening (Round 1)","Interview (Round 2)","Interview (Round 3)","Final Interview","Offer","Background Check Passed","Started","Completed","Failed"]},"followUpDate":{"type":"string"},"resumeVersion":{"type":"string"},"notes":{"type":"string"},"commitmentDetails":{"type":"object","properties":{"startDate":{"type":"string"},"endDate":{"type":"string"},"contractType":{"type":"string","minLength":1,"maxLength":100},"agreedRate":{"type":"number","minimum":0,"maximum":10000000}},"required":["startDate","contractType","agreedRate"],"additionalProperties":false},"interviews":{"type":"array","items":{"type":"object","properties":{"stage":{"type":"string","enum":["Screening (Round 1)","Interview (Round 2)","Interview (Round 3)","Final Interview"]},"dateTime":{"type":"string"}},"required":["stage","dateTime"],"additionalProperties":false}},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"updatedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"}},"required":["id","company","role","location","bidDate","status","createdAt","updatedAt"],"additionalProperties":false}},"profile":{"type":"object","properties":{"firstName":{"type":"string"},"lastName":{"type":"string"},"dateOfBirth":{"type":"string"},"location":{"type":"string"},"email":{"type":"string"},"phone":{"type":"string"},"photoUrl":{"description":"A signed, short-lived link to the candidate photo. Absent when no photo has been uploaded, or when this deployment has no S3 bucket configured. Read-only: PUT ignores it.","type":"string","format":"uri"},"workExperience":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","maxLength":64},"company":{"default":"","type":"string","maxLength":200},"location":{"default":"","type":"string","maxLength":200},"startDate":{"default":""},"endDate":{"default":null}},"required":["id","company","location","startDate","endDate"],"additionalProperties":false}},"education":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","maxLength":64},"degree":{"default":"","type":"string","maxLength":200},"major":{"default":"","type":"string","maxLength":200},"university":{"default":"","type":"string","maxLength":200},"location":{"default":"","type":"string","maxLength":200},"startDate":{"default":""},"endDate":{"default":null}},"required":["id","degree","major","university","location","startDate","endDate"],"additionalProperties":false}}},"required":["firstName","lastName","dateOfBirth","location","email","phone","workExperience","education"],"additionalProperties":false},"settings":{"type":"object","properties":{"dailyBidGoal":{"default":100,"type":"integer","minimum":0,"maximum":10000},"theme":{"default":"light","type":"string","enum":["light","dark"]}},"required":["dailyBidGoal","theme"],"additionalProperties":false}},"required":["bids","profile","settings"],"additionalProperties":false},"ImportBundle":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"bids":{"maxItems":10000,"type":"array","items":{"type":"object","properties":{"company":{"type":"string","minLength":1,"maxLength":200},"role":{"type":"string","minLength":1,"maxLength":200},"jobUrl":{"type":"string","maxLength":2048,"format":"uri"},"location":{"default":"","type":"string","maxLength":200},"jobDescription":{"type":"string","maxLength":50000},"rateType":{"type":"string","enum":["hourly","annually"]},"rateAmount":{"type":"number","minimum":0,"maximum":10000000},"bidDate":{"type":"string"},"status":{"default":"Draft","type":"string","enum":["Draft","Applied","Screening (Round 1)","Interview (Round 2)","Interview (Round 3)","Final Interview","Offer","Background Check Passed","Started","Completed","Failed"]},"followUpDate":{"type":"string"},"resumeVersion":{"type":"string","maxLength":200},"notes":{"type":"string","maxLength":20000},"interviews":{"maxItems":4,"type":"array","items":{"type":"object","properties":{"stage":{"type":"string","enum":["Screening (Round 1)","Interview (Round 2)","Interview (Round 3)","Final Interview"]},"dateTime":{"type":"string"}},"required":["stage","dateTime"]}},"commitmentDetails":{"type":"object","properties":{"startDate":{"type":"string"},"endDate":{"type":"string"},"contractType":{"type":"string","minLength":1,"maxLength":100},"agreedRate":{"type":"number","minimum":0,"maximum":10000000}},"required":["startDate","contractType","agreedRate"]},"resumeDocument":{"anyOf":[{"type":"object","properties":{"company":{"type":"string","description":"The company applied to, echoed back."},"role":{"type":"string","description":"The role applied for, echoed back."},"roleTitles":{"type":"array","items":{"type":"string"},"description":"One title per employer, newest first, aligned with `bullets`."},"summary":{"type":"string","description":"One continuous paragraph. No newlines, no bullets."},"skills":{"type":"array","items":{"type":"string"},"description":"One line per category, e.g. \"Languages: Go | Python | Java | TypeScript\"."},"bullets":{"type":"array","items":{"type":"array","items":{"type":"string"}},"description":"One group per employer, newest first, aligned with `roleTitles`."}},"required":["company","role","roleTitles","summary","skills","bullets"]},{"type":"null"}]},"coverLetterDocument":{"anyOf":[{"type":"object","properties":{"company":{"type":"string","description":"The company applied to, echoed back."},"role":{"type":"string","description":"The role applied for, echoed back."},"greeting":{"type":"string","description":"The salutation, e.g. \"Dear Hiring Manager,\"."},"paragraphs":{"type":"array","items":{"type":"string"},"description":"The body, one continuous paragraph per entry: the Hook, the Core Value Proposition, the Remote Proof and the Call to Action. `**bold**` is the only markup allowed inside them."},"closing":{"type":"string","description":"The sign-off above the name, e.g. \"Sincerely,\"."}},"required":["company","role","greeting","paragraphs","closing"]},{"type":"null"}]},"applicationChat":{"anyOf":[{"maxItems":100,"type":"array","items":{"type":"object","properties":{"role":{"type":"string","enum":["user","assistant"]},"content":{"type":"string","minLength":1,"maxLength":20000}},"required":["role","content"]}},{"type":"null"}]},"userId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["company","role","bidDate"]}},"profile":{"type":"object","properties":{"firstName":{"default":"","type":"string","maxLength":100},"lastName":{"default":"","type":"string","maxLength":100},"dateOfBirth":{"default":"","type":"string","maxLength":10},"location":{"default":"","type":"string","maxLength":200},"email":{"default":"","type":"string","maxLength":254},"phone":{"default":"","type":"string","maxLength":40},"workExperience":{"maxItems":100,"type":"array","items":{"type":"object","properties":{"id":{"type":"string","maxLength":64},"company":{"default":"","type":"string","maxLength":200},"location":{"default":"","type":"string","maxLength":200},"startDate":{"type":"string","maxLength":10},"endDate":{"type":["string","null"]}}}},"education":{"maxItems":100,"type":"array","items":{"type":"object","properties":{"id":{"type":"string","maxLength":64},"degree":{"default":"","type":"string","maxLength":200},"major":{"default":"","type":"string","maxLength":200},"university":{"default":"","type":"string","maxLength":200},"location":{"default":"","type":"string","maxLength":200},"startDate":{"type":"string","maxLength":10},"endDate":{"type":["string","null"]}}}}}},"settings":{"type":"object","properties":{"dailyBidGoal":{"default":100,"type":"integer","minimum":0,"maximum":10000},"theme":{"default":"light","type":"string","enum":["light","dark"]}}}}},"ImportResult":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"mode":{"type":"string","enum":["merge","replace"]},"bidsImported":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"bidsDeleted":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"profileImported":{"type":"boolean"},"settingsImported":{"type":"boolean"}},"required":["mode","bidsImported","bidsDeleted","profileImported","settingsImported"],"additionalProperties":false},"GenerateResumeRequest":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"company":{"type":"string","minLength":1,"maxLength":200},"role":{"type":"string","minLength":1,"maxLength":200},"jobDescription":{"type":"string","minLength":1,"maxLength":50000}},"required":["company","role","jobDescription"]},"RenderResumeRequest":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"document":{"type":"object","properties":{"company":{"type":"string","description":"The company applied to, echoed back."},"role":{"type":"string","description":"The role applied for, echoed back."},"roleTitles":{"type":"array","items":{"type":"string"},"description":"One title per employer, newest first, aligned with `bullets`."},"summary":{"type":"string","description":"One continuous paragraph. No newlines, no bullets."},"skills":{"type":"array","items":{"type":"string"},"description":"One line per category, e.g. \"Languages: Go | Python | Java | TypeScript\"."},"bullets":{"type":"array","items":{"type":"array","items":{"type":"string"}},"description":"One group per employer, newest first, aligned with `roleTitles`."}},"required":["company","role","roleTitles","summary","skills","bullets"]},"format":{"type":"string","enum":["pdf","docx"]},"userId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["document","format"]},"GeneratedResume":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"document":{"type":"object","properties":{"company":{"type":"string","description":"The company applied to, echoed back."},"role":{"type":"string","description":"The role applied for, echoed back."},"roleTitles":{"type":"array","items":{"type":"string"},"description":"One title per employer, newest first, aligned with `bullets`."},"summary":{"type":"string","description":"One continuous paragraph. No newlines, no bullets."},"skills":{"type":"array","items":{"type":"string"},"description":"One line per category, e.g. \"Languages: Go | Python | Java | TypeScript\"."},"bullets":{"type":"array","items":{"type":"array","items":{"type":"string"}},"description":"One group per employer, newest first, aligned with `roleTitles`."}},"required":["company","role","roleTitles","summary","skills","bullets"],"additionalProperties":false}},"required":["document"],"additionalProperties":false},"GenerateCoverLetterRequest":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"company":{"type":"string","minLength":1,"maxLength":200},"role":{"type":"string","minLength":1,"maxLength":200},"jobDescription":{"type":"string","minLength":1,"maxLength":50000},"resume":{"type":"object","properties":{"company":{"type":"string","description":"The company applied to, echoed back."},"role":{"type":"string","description":"The role applied for, echoed back."},"roleTitles":{"type":"array","items":{"type":"string"},"description":"One title per employer, newest first, aligned with `bullets`."},"summary":{"type":"string","description":"One continuous paragraph. No newlines, no bullets."},"skills":{"type":"array","items":{"type":"string"},"description":"One line per category, e.g. \"Languages: Go | Python | Java | TypeScript\"."},"bullets":{"type":"array","items":{"type":"array","items":{"type":"string"}},"description":"One group per employer, newest first, aligned with `roleTitles`."}},"required":["company","role","roleTitles","summary","skills","bullets"]}},"required":["company","role","jobDescription","resume"]},"RenderCoverLetterRequest":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"document":{"type":"object","properties":{"company":{"type":"string","description":"The company applied to, echoed back."},"role":{"type":"string","description":"The role applied for, echoed back."},"greeting":{"type":"string","description":"The salutation, e.g. \"Dear Hiring Manager,\"."},"paragraphs":{"type":"array","items":{"type":"string"},"description":"The body, one continuous paragraph per entry: the Hook, the Core Value Proposition, the Remote Proof and the Call to Action. `**bold**` is the only markup allowed inside them."},"closing":{"type":"string","description":"The sign-off above the name, e.g. \"Sincerely,\"."}},"required":["company","role","greeting","paragraphs","closing"]},"format":{"type":"string","enum":["pdf","docx"]},"userId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["document","format"]},"GeneratedCoverLetter":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"document":{"type":"object","properties":{"company":{"type":"string","description":"The company applied to, echoed back."},"role":{"type":"string","description":"The role applied for, echoed back."},"greeting":{"type":"string","description":"The salutation, e.g. \"Dear Hiring Manager,\"."},"paragraphs":{"type":"array","items":{"type":"string"},"description":"The body, one continuous paragraph per entry: the Hook, the Core Value Proposition, the Remote Proof and the Call to Action. `**bold**` is the only markup allowed inside them."},"closing":{"type":"string","description":"The sign-off above the name, e.g. \"Sincerely,\"."}},"required":["company","role","greeting","paragraphs","closing"],"additionalProperties":false}},"required":["document"],"additionalProperties":false},"ChatRequest":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"company":{"type":"string","minLength":1,"maxLength":200},"role":{"type":"string","minLength":1,"maxLength":200},"jobDescription":{"type":"string","minLength":1,"maxLength":50000},"document":{"type":"object","properties":{"company":{"type":"string","description":"The company applied to, echoed back."},"role":{"type":"string","description":"The role applied for, echoed back."},"roleTitles":{"type":"array","items":{"type":"string"},"description":"One title per employer, newest first, aligned with `bullets`."},"summary":{"type":"string","description":"One continuous paragraph. No newlines, no bullets."},"skills":{"type":"array","items":{"type":"string"},"description":"One line per category, e.g. \"Languages: Go | Python | Java | TypeScript\"."},"bullets":{"type":"array","items":{"type":"array","items":{"type":"string"}},"description":"One group per employer, newest first, aligned with `roleTitles`."}},"required":["company","role","roleTitles","summary","skills","bullets"]},"messages":{"minItems":1,"maxItems":100,"type":"array","items":{"type":"object","properties":{"role":{"type":"string","enum":["user","assistant"]},"content":{"type":"string","minLength":1,"maxLength":20000}},"required":["role","content"]}}},"required":["company","role","jobDescription","messages"]},"ChatTranscript":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"messages":{"maxItems":100,"type":"array","items":{"type":"object","properties":{"role":{"type":"string","enum":["user","assistant"]},"content":{"type":"string","minLength":1,"maxLength":20000}},"required":["role","content"],"additionalProperties":false}}},"required":["messages"],"additionalProperties":false},"ListModelsRequest":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"provider":{"type":"string","enum":["openai","anthropic","gemini"]},"apiKey":{"type":"string","minLength":1,"maxLength":500}},"required":["provider"]},"AvailableModels":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"provider":{"type":"string","enum":["openai","anthropic","gemini"]},"models":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"displayName":{"type":"string"}},"required":["id","displayName"],"additionalProperties":false}},"defaultModel":{"type":"string"}},"required":["provider","models","defaultModel"],"additionalProperties":false},"ParseJobRequest":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"url":{"type":"string","maxLength":2048,"format":"uri"}},"required":["url"]},"ParsedJob":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"url":{"type":"string","format":"uri"},"description":{"type":"string"},"truncated":{"type":"boolean"},"strategy":{"type":"string","enum":["json-ld","container","readability","none"]},"title":{"type":"string"},"company":{"type":"string"},"location":{"type":"string"},"employmentType":{"type":"string"},"datePosted":{"type":"string"}},"required":["url","description","truncated","strategy"],"additionalProperties":false},"AdminUser":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"role":{"type":"string","enum":["user","admin"]},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"firstName":{"type":"string"},"lastName":{"type":"string"},"displayName":{"type":"string"},"photoUrl":{"type":"string","format":"uri"},"dateOfBirth":{"type":"string"},"bidCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["id","email","role","createdAt","firstName","lastName","displayName","dateOfBirth","bidCount"],"additionalProperties":false},"AdminUserDetail":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"role":{"type":"string","enum":["user","admin"]},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"firstName":{"type":"string"},"lastName":{"type":"string"},"displayName":{"type":"string"},"photoUrl":{"type":"string","format":"uri"},"dateOfBirth":{"type":"string"},"bidCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"profile":{"type":"object","properties":{"firstName":{"type":"string"},"lastName":{"type":"string"},"dateOfBirth":{"type":"string"},"location":{"type":"string"},"email":{"type":"string"},"phone":{"type":"string"},"photoUrl":{"description":"A signed, short-lived link to the candidate photo. Absent when no photo has been uploaded, or when this deployment has no S3 bucket configured. Read-only: PUT ignores it.","type":"string","format":"uri"},"workExperience":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","maxLength":64},"company":{"default":"","type":"string","maxLength":200},"location":{"default":"","type":"string","maxLength":200},"startDate":{"default":""},"endDate":{"default":null}},"required":["id","company","location","startDate","endDate"],"additionalProperties":false}},"education":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","maxLength":64},"degree":{"default":"","type":"string","maxLength":200},"major":{"default":"","type":"string","maxLength":200},"university":{"default":"","type":"string","maxLength":200},"location":{"default":"","type":"string","maxLength":200},"startDate":{"default":""},"endDate":{"default":null}},"required":["id","degree","major","university","location","startDate","endDate"],"additionalProperties":false}}},"required":["firstName","lastName","dateOfBirth","location","email","phone","workExperience","education"],"additionalProperties":false}},"required":["id","email","role","createdAt","firstName","lastName","displayName","dateOfBirth","bidCount","profile"],"additionalProperties":false},"CreateUser":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"email":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"password":{"type":"string","minLength":12,"maxLength":200},"firstName":{"type":"string","minLength":1,"maxLength":100},"lastName":{"type":"string","minLength":1,"maxLength":100},"dateOfBirth":{"default":"","type":"string","maxLength":10},"role":{"default":"user","type":"string","enum":["user","admin"]}},"required":["email","password","firstName","lastName"]},"UpdateUser":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"email":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"password":{"type":"string","minLength":12,"maxLength":200},"role":{"type":"string","enum":["user","admin"]}}},"AiSettings":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"provider":{"type":"string","enum":["openai","anthropic","gemini"]},"model":{"type":"string"},"systemPrompt":{"type":"string"},"coverLetterPrompt":{"type":"string"},"hasApiKey":{"type":"boolean"},"keySource":{"type":"string","enum":["database","environment","none"]},"overrides":{"type":"object","properties":{"provider":{"type":"boolean"},"model":{"type":"boolean"},"systemPrompt":{"type":"boolean"},"coverLetterPrompt":{"type":"boolean"}},"required":["provider","model","systemPrompt","coverLetterPrompt"],"additionalProperties":false},"defaults":{"type":"object","properties":{"provider":{"type":"string","enum":["openai","anthropic","gemini"]},"model":{"type":"string"},"keyVariable":{"type":"string"}},"required":["provider","model","keyVariable"],"additionalProperties":false},"updatedAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},{"type":"null"}]}},"required":["provider","model","systemPrompt","coverLetterPrompt","hasApiKey","keySource","overrides","defaults","updatedAt"],"additionalProperties":false},"AiSettingsInput":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"provider":{"anyOf":[{"type":"string","enum":["openai","anthropic","gemini"]},{"type":"null"}]},"model":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}]},"systemPrompt":{"anyOf":[{"type":"string","maxLength":50000},{"type":"null"}]},"coverLetterPrompt":{"anyOf":[{"type":"string","maxLength":50000},{"type":"null"}]},"apiKey":{"anyOf":[{"type":"string","minLength":1,"maxLength":500},{"type":"null"}]}}},"Error":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false}}},"required":["code","message"],"additionalProperties":false}},"required":["error"],"additionalProperties":false}},"securitySchemes":{"cookieAuth":{"type":"apiKey","in":"cookie","name":"ge_access"},"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"security":[{"cookieAuth":[]},{"bearerAuth":[]}],"paths":{"/health":{"get":{"tags":["Dashboard"],"summary":"Liveness and database connectivity check","security":[],"responses":{"200":{"description":"The service is up."}}}},"/api/auth/register":{"post":{"tags":["Auth"],"summary":"Create an account and start a session","description":"Also creates the résumé profile and default settings the frontend expects to exist. The name and date of birth are written into that profile. Every account is created with role `user`; there is no way to register as an admin.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Register"}}}},"responses":{"201":{"description":"Account created; auth cookies set.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthUser"}}}},"400":{"description":"Request validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"That email is already registered.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many attempts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/auth/login":{"post":{"tags":["Auth"],"summary":"Start a session","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}}},"responses":{"200":{"description":"Signed in; auth cookies set.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthUser"}}}},"401":{"description":"Incorrect email or password.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many attempts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/auth/refresh":{"post":{"tags":["Auth"],"summary":"Rotate the refresh token and issue a new access token","description":"Replaying an already-rotated token revokes every token for that user — it is treated as evidence the token was stolen.","security":[],"responses":{"200":{"description":"New cookies set.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthUser"}}}},"401":{"description":"Missing, expired, or already-used refresh token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/auth/logout":{"post":{"tags":["Auth"],"summary":"Revoke the refresh token and clear cookies","security":[],"responses":{"204":{"description":"Signed out. Calling this twice is not an error."}}}},"/api/auth/me":{"get":{"tags":["Auth"],"summary":"The currently authenticated account","responses":{"200":{"description":"The current user.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthUser"}}}},"401":{"description":"Not authenticated, or the access token has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/bids":{"get":{"tags":["Bids"],"summary":"List bids","description":"An account's own applications — or every profile's, for an admin, each row naming its `candidate`. The query parameters are the server-side equivalents of the Applications page's search and filters.","parameters":[{"name":"search","in":"query","schema":{"type":"string"},"description":"Case-insensitive substring match on company or role."},{"name":"status","in":"query","schema":{"type":"string","enum":["Draft","Applied","Screening (Round 1)","Interview (Round 2)","Interview (Round 3)","Final Interview","Offer","Background Check Passed","Started","Completed","Failed"]},"description":"Exact status match."},{"name":"from","in":"query","schema":{"type":"string"},"description":"Earliest bidDate, inclusive (YYYY-MM-DD)."},{"name":"to","in":"query","schema":{"type":"string"},"description":"Latest bidDate, inclusive (YYYY-MM-DD)."}],"responses":{"200":{"description":"Newest bid date first.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Bid"}}}}},"400":{"description":"Request validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated, or the access token has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Bids"],"summary":"Create a bid","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateBid"}}}},"responses":{"201":{"description":"The created bid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Bid"}}}},"400":{"description":"Request validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated, or the access token has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"An interview stage was requested without a matching scheduled interview round.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/bids/{id}":{"get":{"tags":["Bids"],"summary":"Fetch one bid","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The bid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Bid"}}}},"400":{"description":"Request validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated, or the access token has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such bid, or it belongs to another account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Bids"],"summary":"Update a bid","description":"Partial update. Supplying `interviews` replaces the whole set; `commitmentDetails: null` clears the commitment. Unknown keys — including `id`, `createdAt` and `updatedAt` — are ignored.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateBid"}}}},"responses":{"200":{"description":"The updated bid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Bid"}}}},"400":{"description":"Request validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated, or the access token has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such bid, or it belongs to another account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The resulting status is an interview stage with no scheduled round.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Bids"],"summary":"Delete a bid","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Deleted."},"400":{"description":"Request validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated, or the access token has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such bid, or it belongs to another account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/bids/bulk/status":{"post":{"tags":["Bids"],"summary":"Move many bids to one status","description":"Ids not owned by the caller are skipped silently. Moving into an interview stage requires `interviewDateTime`, which is applied to every selected bid.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkStatus"}}}},"responses":{"200":{"description":"How many bids were updated.","content":{"application/json":{"schema":{"type":"object","properties":{"updated":{"type":"integer"}}}}}},"400":{"description":"Request validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated, or the access token has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"An interview stage was requested without `interviewDateTime`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/bids/bulk/delete":{"post":{"tags":["Bids"],"summary":"Delete many bids","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkDelete"}}}},"responses":{"200":{"description":"How many bids were deleted.","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"integer"}}}}}},"400":{"description":"Request validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated, or the access token has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/jobs/parse":{"post":{"tags":["Jobs"],"summary":"Read a job posting from its URL and return it as Markdown","description":"Fetches the page, finds the posting — schema.org `JobPosting` JSON-LD first, then a\nknown container, then the densest block of non-link text — and converts it to\nMarkdown for the application form to fill in. Nothing is stored: the result becomes a\nbid only if the user saves one.\n\nOnly public `http(s)` URLs are fetched. A URL that resolves to a private or loopback\naddress is rejected, as is one that redirects to such an address.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParseJobRequest"}}}},"responses":{"200":{"description":"The posting, as Markdown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParsedJob"}}}},"400":{"description":"Request validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated, or the access token has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The URL is not fetchable (private address, wrong content type), or the page carries no job description.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many URLs parsed in the last minute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"The site could not be reached, timed out, or refused the request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/bids/{id}/chat":{"get":{"tags":["Bids"],"summary":"The conversation held while filling in this application","description":"Its own request rather than a field on the bid, for the same reason the résumé is:\nevery screen loads the whole bid list, and a transcript per row would be paid for by\nall of them and read by almost none. The bid carries `chatMessageCount`; this\ncarries the text.\n\nAn application that had no conversation answers with an empty list rather than 404.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The transcript, oldest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatTranscript"}}}},"400":{"description":"Request validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated, or the access token has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such application.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/bids/{id}/resume":{"get":{"tags":["Bids"],"summary":"Download an application's stored résumé","description":"Renders the résumé saved against this bid, as PDF or as Word.\n\nCosts nothing at the AI platform: the document was stored when the application was\nsaved, so the same one downloads as either format, as often as wanted — and it is\nalways the résumé actually generated for this application rather than a fresh one,\nwhich would say something different.\n\nA bid with no stored résumé answers `404`: it exists, it simply predates résumé\ngeneration or was saved without one. `hasResume` on the bid says which.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"format","in":"query","required":false,"schema":{"type":"string","enum":["pdf","docx"],"default":"pdf"}}],"responses":{"200":{"description":"The résumé file.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}},"application/vnd.openxmlformats-officedocument.wordprocessingml.document":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"Request validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated, or the access token has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such application, or no résumé stored against it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"The PDF could not be printed. Word still works.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/bids/{id}/cover-letter":{"get":{"tags":["Bids"],"summary":"Download an application's stored cover letter","description":"Renders the cover letter saved against this bid, as PDF or as Word — the résumé\nroute's twin, on the same terms.\n\nCosts nothing at the AI platform: the letter was stored when the application was\nsaved, so the same one downloads as either format, as often as wanted.\n\nThe letter is dated with the application's own `bidDate`, not the day it is\ndownloaded — a letter printed months later should still say when it was written.\n\nA bid with no stored letter answers `404`: writing one is an optional step, so most\napplications have none. `hasCoverLetter` on the bid says which.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"format","in":"query","required":false,"schema":{"type":"string","enum":["pdf","docx"],"default":"pdf"}}],"responses":{"200":{"description":"The cover letter file.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}},"application/vnd.openxmlformats-officedocument.wordprocessingml.document":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"Request validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated, or the access token has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such application, or no cover letter stored against it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"The PDF could not be printed. Word still works.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/questions/chat":{"post":{"tags":["Questions"],"summary":"Continue the conversation about one application","description":"The chat behind the application form's drawer: the free-text boxes an employer asks\nbeside the résumé upload, answered in the applicant's own voice and ready to paste.\n\n**Answers as `text/event-stream`, not JSON.** A paragraph that arrives all at once\ntwenty seconds later reads as a hung page. The frames are `delta` (`{ text }`, a\nfragment of the reply), `done` (the reply is complete) and `error` (`{ message }`).\nA stream that ends without `done` failed — treat it as one.\n\n**A rejected request is still a status.** A blank message, a missing job description\nor an unconfigured platform is answered as JSON with a 4xx or 5xx, because nothing\nhas been written yet. Only a failure *after* the reply began is an `error` frame:\nby then the status line is long gone.\n\n**Nothing is stored, and there is no session.** The application and the whole\ntranscript travel on every turn, which is what lets the drawer work on an\napplication that has not been saved yet, and what lets the résumé change\nmid-conversation with nothing to invalidate. The transcript is saved with the bid\nas `applicationChat` when the form is saved.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatRequest"}}}},"responses":{"200":{"description":"The reply, streamed.","content":{"text/event-stream":{"schema":{"type":"string"},"example":"event: delta\ndata: {\"text\":\"6 years\"}\n\nevent: delta\ndata: {\"text\":\", most recently at \"}\n\nevent: done\ndata: {}\n\n"}}},"400":{"description":"Request validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated, or the access token has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many messages in the last minute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"No API key is configured for the selected AI provider.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/resume/generate":{"post":{"tags":["Resume"],"summary":"Write the résumé's content for one application","description":"Sends the company, the job title and the job description to the configured AI\nplatform — OpenAI, Anthropic or Google, chosen by `AI_PROVIDER` — and returns the\ndocument it writes. Rendering that document into a file is `/api/resume/render`.\n\nThe system prompt is server-side and is never returned by this or any other route.\nThe three fields in the request body are the only thing about the application the\nmodel is given — the profile is not sent, and is only used when rendering.\n\nNothing is stored. The `X-Resume-Provider` and `X-Resume-Model` response headers\nname what wrote it.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateResumeRequest"}}}},"responses":{"200":{"description":"The résumé's content.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeneratedResume"}}}},"400":{"description":"Request validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated, or the access token has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many résumés generated in the last minute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"The AI platform failed, declined, or returned an unusable résumé.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"No API key is configured for the selected AI provider.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/resume/render":{"post":{"tags":["Resume"],"summary":"Render a generated résumé as PDF or Word","description":"Lays the generated document out in the résumé template and returns the file.\n\nSeparate from generation so that a second format costs nothing: the AI is paid for\nonce and both files come from the one answer.\n\nThe document travels with the request; the name, contact line, employers and\neducation come from the signed-in account's profile and cannot be supplied by a\nclient. `pdf` is printed from the template's own stylesheet through headless\nChromium, `docx` is built with the same measurements stated in Word's terms.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenderResumeRequest"}}}},"responses":{"200":{"description":"The résumé file.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}},"application/vnd.openxmlformats-officedocument.wordprocessingml.document":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"Request validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated, or the access token has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many résumés rendered in the last minute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"The PDF could not be printed. Word still works.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/resume/models":{"post":{"tags":["Resume"],"summary":"List the models a platform will let this key use","description":"For the model picker: choose a platform, supply a key, pick from what comes back.\n\n`apiKey` is optional. Supplied, it is used for this one call and discarded — nothing\nstores it and nothing logs it, which is what lets a key be checked before it is\nsaved anywhere. Omitted, the key configured for that platform is used instead, so\nthe route is usable before any admin screen exists.\n\nOnly models that can write a résumé are returned: Anthropic lists nothing else,\nGoogle's entries are filtered on the `generateContent` capability they declare, and\nOpenAI's are filtered by excluding the families that cannot — embeddings, speech,\nimages and moderation — so that a chat model released tomorrow still appears.\n\n`defaultModel` is what a picker should land on. It is not a promise that the list\ncontains it.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListModelsRequest"}}}},"responses":{"200":{"description":"The models this key can use.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AvailableModels"}}}},"400":{"description":"The body is invalid, or the platform rejected the API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated, or the access token has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many model lookups in the last minute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"The platform could not be reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"No API key was supplied and none is configured for that platform.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/cover-letter/generate":{"post":{"tags":["Cover letter"],"summary":"Write the cover letter's content for one application","description":"The step after the résumé. Sends the company, the job title, the job description\n**and the finished résumé** to the configured AI platform, and returns the letter it\nwrites. Rendering that letter into a file is `/api/cover-letter/render`.\n\nThe résumé travels in the body rather than being looked up, because at this point\nit usually does not exist on the server yet: on the application form it has not been\nsaved, and on the JSON route it was pasted in rather than generated here. It is the\ndocument `/api/resume/generate` answered with, handed straight back.\n\n**A second call rather than a second field on the résumé's answer.** Asking for both\nat once would mean a letter that could not be rewritten without also rewriting the\nrésumé it was approved alongside — and it is what lets the letter genuinely be\nwritten *from* the finished résumé, which is what the prompt asks for.\n\nIts own system prompt, edited separately from the résumé's on the AI Settings\nscreen, and returned by neither this route nor any other. Nothing is stored; the\n`X-Resume-Provider` and `X-Resume-Model` response headers name what wrote it.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateCoverLetterRequest"}}}},"responses":{"200":{"description":"The cover letter's content.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeneratedCoverLetter"}}}},"400":{"description":"Request validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated, or the access token has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many cover letters generated in the last minute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"The AI platform failed, declined, or returned an unusable cover letter.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"No API key is configured for the selected AI provider.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/cover-letter/render":{"post":{"tags":["Cover letter"],"summary":"Render a generated cover letter as PDF or Word","description":"Lays the generated letter out in the cover letter template and returns the file.\n\nSeparate from generation so that a second format costs nothing: the AI is paid for\nonce and both files come from the one answer.\n\nThe letter travels with the request; the name at the top, the contact line and the\nsignature come from the signed-in account's profile and cannot be supplied by a\nclient — nobody signs someone else's name to a letter through this API.\n\nThe page is the résumé's: US Letter, Word's Narrow margins, Calibri bound to an\nembedded metric-compatible face. `pdf` is printed from the template's own\nstylesheet through headless Chromium, `docx` is built with the same measurements\nstated in Word's terms.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenderCoverLetterRequest"}}}},"responses":{"200":{"description":"The cover letter file.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}},"application/vnd.openxmlformats-officedocument.wordprocessingml.document":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"Request validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated, or the access token has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many cover letters rendered in the last minute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"The PDF could not be printed. Word still works.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/profile":{"get":{"tags":["Profile"],"summary":"The résumé profile","responses":{"200":{"description":"The profile.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Profile"}}}},"401":{"description":"Not authenticated, or the access token has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"tags":["Profile"],"summary":"Replace the résumé profile","description":"A full replace, not a merge — work history and education are rewritten in the order given.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProfileInput"}}}},"responses":{"200":{"description":"The saved profile.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Profile"}}}},"400":{"description":"Request validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated, or the access token has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/profile/photo":{"post":{"tags":["Profile"],"summary":"Upload the candidate photo","description":"Multipart, one file in the `photo` field: JPEG, PNG, WebP or GIF, at most 5 MB. Replaces whatever photo is stored and deletes the old object. The whole profile comes back, with a freshly signed `photoUrl`.","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["photo"],"properties":{"photo":{"type":"string","format":"binary"}}}}}},"responses":{"200":{"description":"The profile, with the new photo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Profile"}}}},"400":{"description":"No file, an unsupported type, or larger than 5 MB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated, or the access token has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"This account has no profile.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"No S3 bucket is configured for this deployment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Profile"],"summary":"Remove the candidate photo","description":"Deleting a photo that is not there is not an error.","responses":{"200":{"description":"The profile, with no photo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Profile"}}}},"401":{"description":"Not authenticated, or the access token has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"This account has no profile.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/admin/users":{"get":{"tags":["Admin"],"summary":"Every account, with its profile summary","description":"What the profile switcher and the user list on the Profile screen read. `bidCount` is how many applications are filed under each account.","responses":{"200":{"description":"All accounts.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AdminUser"}}}}},"401":{"description":"Not authenticated, or the access token has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"This action is restricted to administrators.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Admin"],"summary":"Create an account","description":"Creates the user, its résumé profile and its default settings together, exactly as registration does — the only difference is that this can set a role.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUser"}}}},"responses":{"201":{"description":"The new account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminUserDetail"}}}},"400":{"description":"Request validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated, or the access token has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"This action is restricted to administrators.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"That email is already registered.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/admin/users/{id}":{"get":{"tags":["Admin"],"summary":"One account and its full profile","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminUserDetail"}}}},"401":{"description":"Not authenticated, or the access token has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"This action is restricted to administrators.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Admin"],"summary":"Change an account's email, password or role","description":"Every field is optional; send only what changes. A new password also revokes that account's live refresh tokens. An admin cannot change their own role.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUser"}}}},"responses":{"200":{"description":"The updated account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminUserDetail"}}}},"400":{"description":"Nothing to change, or you tried to change your own role.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated, or the access token has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"This action is restricted to administrators.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Another account already uses that email.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Admin"],"summary":"Delete an account and everything it owns","description":"Cascades to bids, interviews, commitments, profile, settings and sessions. An admin cannot delete their own account.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Deleted."},"400":{"description":"You cannot delete your own account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated, or the access token has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"This action is restricted to administrators.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/admin/users/{id}/profile":{"put":{"tags":["Admin"],"summary":"Replace another account's résumé profile","description":"The same full replace as `PUT /api/profile`, and the same service behind it — an admin editing a profile and its owner editing it are the same write.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProfileInput"}}}},"responses":{"200":{"description":"The saved profile.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Profile"}}}},"400":{"description":"Request validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated, or the access token has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"This action is restricted to administrators.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/admin/users/{id}/profile/photo":{"post":{"tags":["Admin"],"summary":"Upload another account's candidate photo","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["photo"],"properties":{"photo":{"type":"string","format":"binary"}}}}}},"responses":{"200":{"description":"The profile, with the new photo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Profile"}}}},"400":{"description":"No file, an unsupported type, or larger than 5 MB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated, or the access token has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"This action is restricted to administrators.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"No S3 bucket is configured for this deployment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Admin"],"summary":"Remove another account's candidate photo","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The profile, with no photo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Profile"}}}},"401":{"description":"Not authenticated, or the access token has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"This action is restricted to administrators.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/admin/ai":{"get":{"tags":["Admin"],"summary":"The deployment-wide AI settings","description":"Provider, model and system prompt in force, plus where the API key comes from. The key itself is never returned — `hasApiKey` and `keySource` are all there is. `overrides` says which fields are stored here rather than coming from the environment, and `defaults` is what clearing one reverts to.","responses":{"200":{"description":"The settings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AiSettings"}}}},"401":{"description":"Not authenticated, or the access token has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"This action is restricted to administrators.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"tags":["Admin"],"summary":"Change the AI settings","description":"A partial update. An omitted field is left alone; an explicit `null` clears that override and hands the field back to the environment. `apiKey` is write-only: a string replaces the stored key, `null` deletes it.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AiSettingsInput"}}}},"responses":{"200":{"description":"The settings after the change.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AiSettings"}}}},"400":{"description":"Request validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated, or the access token has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"This action is restricted to administrators.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/settings":{"get":{"tags":["Settings"],"summary":"App settings","responses":{"200":{"description":"The settings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppSettings"}}}},"401":{"description":"Not authenticated, or the access token has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"tags":["Settings"],"summary":"Replace app settings","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppSettings"}}}},"responses":{"200":{"description":"The saved settings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppSettings"}}}},"400":{"description":"Request validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated, or the access token has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/dashboard":{"get":{"tags":["Dashboard"],"summary":"Every aggregate the Dashboard screen needs, in one request","parameters":[{"name":"days","in":"query","required":false,"schema":{"default":7,"type":"integer","minimum":1,"maximum":366}},{"name":"weeks","in":"query","required":false,"schema":{"default":12,"type":"integer","minimum":1,"maximum":53}},{"name":"limit","in":"query","required":false,"schema":{"default":5,"type":"integer","minimum":1,"maximum":50}},{"name":"tzOffset","in":"query","required":false,"schema":{"default":0,"type":"integer","minimum":-840,"maximum":840}}],"responses":{"200":{"description":"The summary.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Dashboard"}}}},"400":{"description":"Request validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated, or the access token has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/data/export":{"get":{"tags":["Data"],"summary":"Export the whole account","description":"Same JSON shape as the frontend's own `global-eyes-backup.json`.","responses":{"200":{"description":"The full dataset.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataBundle"}}}},"401":{"description":"Not authenticated, or the access token has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/data/import":{"post":{"tags":["Data"],"summary":"Restore an exported backup","parameters":[{"name":"mode","in":"query","schema":{"type":"string","enum":["merge","replace"],"default":"merge"},"description":"`replace` deletes existing bids first; `merge` adds alongside them."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportBundle"}}}},"responses":{"200":{"description":"What was imported.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportResult"}}}},"400":{"description":"Request validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated, or the access token has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"A bid in the bundle violates a business rule.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/data":{"delete":{"tags":["Data"],"summary":"Delete all bids and reset the profile and settings","description":"The account itself survives. This is \"Clear All Data\", server-side.","responses":{"200":{"description":"How many bids were deleted.","content":{"application/json":{"schema":{"type":"object","properties":{"bidsDeleted":{"type":"integer"}}}}}},"401":{"description":"Not authenticated, or the access token has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}