openapi: 3.1.0
info:
  title: OphyAI Public API
  version: 0.1.0-preview
  summary: Real-time interview, resume, and job-search APIs for AI agents and copilots.
  description: |
    **Status: v0 — preview / coming soon.**

    The OphyAI Public API exposes the same capabilities that power the OphyAI
    web app — real-time interview answers, resume analysis, ATS tailoring,
    cover letter generation, STAR-format behavioral answers, and job search —
    so they can be invoked by AI agents (ChatGPT custom GPTs, Microsoft 365
    Copilot declarative agents, MCP servers, custom LLM agents).

    This OpenAPI document is the canonical reference used by our ChatGPT
    Custom GPT (`/integrations/chatgpt`) and Microsoft 365 Copilot
    declarative agent (`/integrations/copilot`). The endpoints described
    here are stabilizing — exact request/response shapes may shift before
    GA. Contact api@ophyai.com for early access keys.

    Authentication is via Bearer token. API keys are issued from your
    OphyAI account at https://app.ophyai.com (coming soon — sign up at
    https://app.ophyai.com/auth/signup to be notified).
  contact:
    name: OphyAI API Team
    email: api@ophyai.com
    url: https://ophyai.com/integrations
  license:
    name: OphyAI API Terms
    url: https://ophyai.com/terms
servers:
  - url: https://api.ophyai.com
    description: Production (preview)
  - url: https://api.staging.ophyai.com
    description: Staging
security:
  - BearerAuth: []
tags:
  - name: Interview
    description: Real-time and behavioral interview answer generation.
  - name: Resume
    description: Resume analysis, ATS scoring, and tailoring.
  - name: CoverLetter
    description: Cover letter generation.
  - name: Jobs
    description: Job search and matching.
paths:
  /api/interview/answer:
    post:
      operationId: generateInterviewAnswer
      tags: [Interview]
      summary: Generate a real-time interview answer.
      description: |
        Given an interview question and optional resume / job-description
        context, returns a concise, conversation-ready answer the candidate
        can speak aloud. Optimized for low latency and natural delivery.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InterviewAnswerRequest"
      responses:
        "200":
          description: Generated interview answer.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InterviewAnswerResponse"
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/RateLimited" }
  /api/star-answer:
    post:
      operationId: generateStarAnswer
      tags: [Interview]
      summary: Generate a STAR-format behavioral answer.
      description: |
        Given a behavioral interview question and a target role, returns a
        complete STAR (Situation, Task, Action, Result) structured answer.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StarAnswerRequest"
      responses:
        "200":
          description: Generated STAR answer.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StarAnswerResponse"
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/RateLimited" }
  /api/resume/analyze:
    post:
      operationId: analyzeResume
      tags: [Resume]
      summary: Score a resume against a job description.
      description: |
        Returns an ATS compatibility score (0–100), keyword coverage gaps,
        and prioritized improvement suggestions.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ResumeAnalyzeRequest"
      responses:
        "200":
          description: Resume analysis report.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ResumeAnalyzeResponse"
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/RateLimited" }
  /api/resume/tailor:
    post:
      operationId: tailorResume
      tags: [Resume]
      summary: Rewrite a resume to match a job description.
      description: |
        Returns a tailored resume optimized for ATS keyword coverage and
        recruiter readability for the supplied job description.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ResumeTailorRequest"
      responses:
        "200":
          description: Tailored resume.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ResumeTailorResponse"
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/RateLimited" }
  /api/cover-letter/generate:
    post:
      operationId: generateCoverLetter
      tags: [CoverLetter]
      summary: Generate a cover letter.
      description: |
        Generates a tailored cover letter from a resume + job description,
        with optional tone (formal, conversational, enthusiastic).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CoverLetterRequest"
      responses:
        "200":
          description: Generated cover letter.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CoverLetterResponse"
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/RateLimited" }
  /api/jobs/search:
    post:
      operationId: searchJobs
      tags: [Jobs]
      summary: Search jobs by keyword, role, location, and filters.
      description: |
        Returns a paginated list of job matches across the OphyAI job
        index. Supports keyword search, role title, location (country /
        remote), seniority, and posted-within filters.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/JobSearchRequest"
      responses:
        "200":
          description: Job search results.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JobSearchResponse"
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/RateLimited" }
components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API key (issued from app.ophyai.com)
  responses:
    BadRequest:
      description: Invalid request payload.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    Unauthorized:
      description: Missing or invalid API key.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    RateLimited:
      description: Rate limit exceeded.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
  schemas:
    Error:
      type: object
      required: [code, message]
      properties:
        code:
          type: string
          example: invalid_request
        message:
          type: string
          example: "Field 'question' is required."
        request_id:
          type: string
          example: req_01HXYZ...
    InterviewAnswerRequest:
      type: object
      required: [question]
      properties:
        question:
          type: string
          description: The interviewer's question, transcribed.
          example: "Tell me about a time you had to lead a team through a difficult deadline."
        resume_text:
          type: string
          description: Optional plain-text resume of the candidate.
        job_description:
          type: string
          description: Optional job description for the role being interviewed for.
        tone:
          type: string
          enum: [concise, conversational, structured, star]
          default: conversational
        language:
          type: string
          description: BCP 47 language tag for the answer.
          default: en-US
          example: en-US
    InterviewAnswerResponse:
      type: object
      required: [answer]
      properties:
        answer:
          type: string
          description: The generated answer the candidate can speak aloud.
        bullet_points:
          type: array
          items: { type: string }
          description: Optional structured bullets for glanceable delivery.
        sources:
          type: array
          items: { type: string }
          description: Resume snippets the answer is grounded on.
    StarAnswerRequest:
      type: object
      required: [question, role]
      properties:
        question:
          type: string
          example: "Tell me about a time you disagreed with your manager."
        role:
          type: string
          example: "Senior Product Manager at a fintech"
        resume_text:
          type: string
        seed_examples:
          type: array
          items: { type: string }
          description: Optional list of stories the candidate has told before, to ground the answer.
    StarAnswerResponse:
      type: object
      required: [situation, task, action, result, summary]
      properties:
        situation: { type: string }
        task: { type: string }
        action: { type: string }
        result: { type: string }
        summary:
          type: string
          description: One-paragraph delivery-ready version.
    ResumeAnalyzeRequest:
      type: object
      required: [resume_text]
      properties:
        resume_text:
          type: string
        job_description:
          type: string
          description: Optional. If supplied, score is JD-specific.
        target_role:
          type: string
          example: "Senior Backend Engineer"
    ResumeAnalyzeResponse:
      type: object
      required: [ats_score, summary]
      properties:
        ats_score:
          type: integer
          minimum: 0
          maximum: 100
        summary: { type: string }
        missing_keywords:
          type: array
          items: { type: string }
        suggestions:
          type: array
          items:
            type: object
            required: [section, suggestion]
            properties:
              section: { type: string, example: "Experience" }
              suggestion: { type: string }
              priority:
                type: string
                enum: [low, medium, high]
                default: medium
    ResumeTailorRequest:
      type: object
      required: [resume_text, job_description]
      properties:
        resume_text: { type: string }
        job_description: { type: string }
        preserve_truth:
          type: boolean
          default: true
          description: When true, the model is constrained to facts present in the input resume.
    ResumeTailorResponse:
      type: object
      required: [tailored_resume]
      properties:
        tailored_resume: { type: string }
        change_summary:
          type: array
          items: { type: string }
        ats_score_after:
          type: integer
          minimum: 0
          maximum: 100
    CoverLetterRequest:
      type: object
      required: [resume_text, job_description]
      properties:
        resume_text: { type: string }
        job_description: { type: string }
        company_name: { type: string }
        hiring_manager_name: { type: string }
        tone:
          type: string
          enum: [formal, conversational, enthusiastic]
          default: conversational
        length:
          type: string
          enum: [short, standard, long]
          default: standard
    CoverLetterResponse:
      type: object
      required: [cover_letter]
      properties:
        cover_letter: { type: string }
        word_count: { type: integer }
    JobSearchRequest:
      type: object
      properties:
        query:
          type: string
          example: "senior frontend engineer"
        location:
          type: string
          example: "Remote, United States"
        country:
          type: string
          description: ISO-3166 alpha-2 (lowercase).
          example: "us"
        seniority:
          type: string
          enum: [internship, entry, mid, senior, staff, principal, director]
        remote:
          type: boolean
        posted_within_days:
          type: integer
          minimum: 1
          maximum: 90
          default: 14
        limit:
          type: integer
          minimum: 1
          maximum: 50
          default: 20
        cursor:
          type: string
    JobSearchResponse:
      type: object
      required: [jobs]
      properties:
        jobs:
          type: array
          items:
            $ref: "#/components/schemas/JobMatch"
        next_cursor:
          type: string
          nullable: true
    JobMatch:
      type: object
      required: [id, title, company, url]
      properties:
        id: { type: string }
        title: { type: string }
        company: { type: string }
        location: { type: string }
        remote: { type: boolean }
        url: { type: string, format: uri }
        posted_at: { type: string, format: date-time }
        salary_range: { type: string, nullable: true }
        match_score:
          type: integer
          minimum: 0
          maximum: 100
          nullable: true
