openapi: 3.1.0
info:
  title: Loam API
  description: |
    The Loam API allows you to programmatically manage your applicant tracking system.
    
    ## Authentication
    
    All API requests require a Bearer token in the Authorization header:
    
    ```
    Authorization: Bearer lk_your_api_key_here
    ```
    
    You can create API keys in your [Settings > API Keys](https://hireloam.com/settings/api-keys) page.
    
    ## Rate Limits
    
    API requests are rate limited to 1000 requests per hour per API key.
    Rate limit headers are included in all responses:
    
    - `X-RateLimit-Limit`: Maximum requests per hour
    - `X-RateLimit-Remaining`: Requests remaining in current window
    - `X-RateLimit-Reset`: Unix timestamp when the limit resets
  version: 1.0.0
  contact:
    name: Loam Support
    email: support@hireloam.com

servers:
  - url: https://hireloam.com/api/v1
    description: Production

security:
  - bearerAuth: []

tags:
  - name: Jobs
    description: Manage job postings
  - name: Applications
    description: Manage applications and candidates
  - name: Notes
    description: Add notes to applications
  - name: Feedback
    description: Add interview feedback
  - name: Candidates
    description: Manage candidate profiles
  - name: Search
    description: Search candidates
  - name: Sourcing
    description: Sourcing searches, prospects, and shortlists

paths:
  /jobs:
    get:
      operationId: listJobs
      summary: List jobs
      description: List all jobs in your organization. Can filter by status.
      tags:
        - Jobs
      parameters:
        - name: status
          in: query
          description: Filter jobs by status
          schema:
            type: string
            enum: [published, draft, closed]
      responses:
        "200":
          description: List of jobs
          content:
            application/json:
              schema:
                type: object
                properties:
                  jobs:
                    type: array
                    items:
                      $ref: "#/components/schemas/Job"
              example:
                jobs:
                  - id: "550e8400-e29b-41d4-a716-446655440000"
                    title: "Senior Software Engineer"
                    status: "published"
                    department: "Engineering"
                    location: "San Francisco, CA"
                    employment_type: "full_time"
                    public_slug: "senior-software-engineer"
                    created_at: "2024-01-15T10:30:00Z"
                    stages:
                      - id: "stage-1"
                        name: "Inbox"
                        order_index: 0
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"

    post:
      operationId: createJob
      summary: Create job
      description: Create a new job posting in draft status with default pipeline stages.
      tags:
        - Jobs
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - title
              properties:
                title:
                  type: string
                  description: Job title
                description_md:
                  type: string
                  description: Job description in markdown
                department:
                  type: string
                  description: Department name
                location:
                  type: string
                  description: Job location
                employment_type:
                  type: string
                  enum: [full_time, part_time, contract, internship]
                  description: Employment type
                show_salary:
                  type: boolean
                  description: Whether salary is shown publicly
                salary_currency:
                  type: string
                  enum: [USD, EUR, GBP, CAD, AUD, OTHER]
                salary_timeframe:
                  type: string
                  enum: [yearly, monthly, hourly]
                salary_min:
                  type: number
                salary_max:
                  type: number
                equity_min:
                  type: number
                equity_max:
                  type: number
            example:
              title: "Senior Software Engineer"
              department: "Engineering"
              location: "San Francisco, CA"
              employment_type: "full_time"
      responses:
        "201":
          description: Job created
          content:
            application/json:
              schema:
                type: object
                properties:
                  job:
                    $ref: "#/components/schemas/Job"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/InternalError"

  /jobs/{jobId}:
    get:
      operationId: getJob
      summary: Get job details
      description: Get details about a specific job including stages and application counts.
      tags:
        - Jobs
      parameters:
        - name: jobId
          in: path
          required: true
          description: The job ID
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Job details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JobDetail"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

    patch:
      operationId: updateJob
      summary: Update job
      description: Update a job's details. When changing status to `published`, plan-based publish limits are enforced.
      tags:
        - Jobs
      parameters:
        - name: jobId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                description_md:
                  type: string
                status:
                  type: string
                  enum: [draft, published, closed]
                department:
                  type: string
                  nullable: true
                location:
                  type: string
                  nullable: true
                employment_type:
                  type: string
                  nullable: true
                show_salary:
                  type: boolean
                salary_currency:
                  type: string
                  nullable: true
                salary_timeframe:
                  type: string
                  nullable: true
                salary_min:
                  type: number
                  nullable: true
                salary_max:
                  type: number
                  nullable: true
                equity_min:
                  type: number
                  nullable: true
                equity_max:
                  type: number
                  nullable: true
            example:
              status: "published"
              department: "Engineering"
      responses:
        "200":
          description: Job updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  job:
                    $ref: "#/components/schemas/JobDetail"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

    delete:
      operationId: deleteJob
      summary: Delete job
      description: Permanently delete a job and its associated data.
      tags:
        - Jobs
      parameters:
        - name: jobId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Job deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  deleted:
                    type: boolean
              example:
                deleted: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

  /applications:
    get:
      operationId: listApplications
      summary: List applications
      description: List applications, optionally filtered by job or stage.
      tags:
        - Applications
      parameters:
        - name: job_id
          in: query
          description: Filter to a specific job
          schema:
            type: string
            format: uuid
        - name: stage_id
          in: query
          description: Filter to a specific stage
          schema:
            type: string
            format: uuid
        - name: limit
          in: query
          description: Maximum number of results (default 50, max 100)
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
        - name: offset
          in: query
          description: Pagination offset
          schema:
            type: integer
            minimum: 0
            default: 0
      responses:
        "200":
          description: List of applications
          content:
            application/json:
              schema:
                type: object
                properties:
                  applications:
                    type: array
                    items:
                      $ref: "#/components/schemas/Application"
                  pagination:
                    $ref: "#/components/schemas/Pagination"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"

    post:
      operationId: createApplication
      summary: Create application
      description: Add a new candidate to a job. Creates an application in the Inbox stage.
      tags:
        - Applications
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - job_id
                - name
                - email
              properties:
                job_id:
                  type: string
                  format: uuid
                  description: The job ID to add the candidate to
                name:
                  type: string
                  description: Candidate's full name
                email:
                  type: string
                  format: email
                  description: Candidate's email address
                linkedin_url:
                  type: string
                  format: uri
                  description: Candidate's LinkedIn profile URL
                notes:
                  type: string
                  description: Initial notes about the candidate
            example:
              job_id: "550e8400-e29b-41d4-a716-446655440000"
              name: "Jane Smith"
              email: "jane@example.com"
              linkedin_url: "https://linkedin.com/in/janesmith"
              notes: "Referred by John Doe"
      responses:
        "201":
          description: Application created
          content:
            application/json:
              schema:
                type: object
                properties:
                  application:
                    $ref: "#/components/schemas/Application"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

  /applications/{id}:
    get:
      operationId: getApplication
      summary: Get application details
      description: Get full details about an application including candidate info, notes, feedback, and AI profile.
      tags:
        - Applications
      parameters:
        - name: id
          in: path
          required: true
          description: The application ID
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Application details
          content:
            application/json:
              schema:
                type: object
                properties:
                  application:
                    $ref: "#/components/schemas/ApplicationDetail"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

    delete:
      operationId: deleteApplication
      summary: Delete application
      description: Permanently delete an application.
      tags:
        - Applications
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Application deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  deleted:
                    type: boolean
              example:
                deleted: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

  /applications/{id}/notes:
    post:
      operationId: addNote
      summary: Add note
      description: Add a note to an application. Notes are visible to all team members.
      tags:
        - Notes
      parameters:
        - name: id
          in: path
          required: true
          description: The application ID
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - note
              properties:
                note:
                  type: string
                  description: The note content
            example:
              note: "Had a great phone screen. Moving forward with technical interview."
      responses:
        "201":
          description: Note created
          content:
            application/json:
              schema:
                type: object
                properties:
                  note:
                    $ref: "#/components/schemas/Note"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

  /applications/{id}/notes/{noteId}:
    patch:
      operationId: updateNote
      summary: Update note
      description: Update a note's content. Only the note author can edit their notes.
      tags:
        - Notes
      parameters:
        - name: id
          in: path
          required: true
          description: The application ID
          schema:
            type: string
            format: uuid
        - name: noteId
          in: path
          required: true
          description: The note ID
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - note
              properties:
                note:
                  type: string
                  description: Updated note content
            example:
              note: "Updated after second conversation - very strong candidate."
      responses:
        "200":
          description: Note updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  note:
                    $ref: "#/components/schemas/Note"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

    delete:
      operationId: deleteNote
      summary: Delete note
      description: Delete a note. Only the note author can delete their notes.
      tags:
        - Notes
      parameters:
        - name: id
          in: path
          required: true
          description: The application ID
          schema:
            type: string
            format: uuid
        - name: noteId
          in: path
          required: true
          description: The note ID
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Note deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  deleted:
                    type: boolean
              example:
                deleted: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

  /applications/{id}/feedback:
    post:
      operationId: addFeedback
      summary: Add interview feedback
      description: Add interview feedback for a candidate. Can be saved as draft or submitted immediately.
      tags:
        - Feedback
      parameters:
        - name: id
          in: path
          required: true
          description: The application ID
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - feedback
              properties:
                rating:
                  type: string
                  enum: [strong_yes, yes, no, strong_no]
                  description: Overall rating
                interview_type:
                  type: string
                  description: Type of interview (e.g., Phone Screen, Technical, Onsite)
                feedback:
                  type: string
                  description: Detailed feedback content
                submit:
                  type: boolean
                  description: If true, submit immediately. If false, save as draft.
            example:
              rating: "yes"
              interview_type: "Technical"
              feedback: "Strong problem-solving skills. Good communication."
              submit: true
      responses:
        "201":
          description: Feedback created
          content:
            application/json:
              schema:
                type: object
                properties:
                  feedback:
                    $ref: "#/components/schemas/Feedback"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

  /applications/{id}/stage:
    patch:
      operationId: moveStage
      summary: Move application to stage
      description: Move an application to a different stage in the pipeline.
      tags:
        - Applications
      parameters:
        - name: id
          in: path
          required: true
          description: The application ID
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - stage_id
              properties:
                stage_id:
                  type: string
                  format: uuid
                  description: The target stage ID
            example:
              stage_id: "660e8400-e29b-41d4-a716-446655440001"
      responses:
        "200":
          description: Application moved
          content:
            application/json:
              schema:
                type: object
                properties:
                  application:
                    type: object
                    properties:
                      id:
                        type: string
                      stage:
                        $ref: "#/components/schemas/Stage"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

  /candidates/search:
    get:
      operationId: searchCandidates
      summary: Search candidates
      description: Search for candidates by name or email across all applications.
      tags:
        - Search
      parameters:
        - name: q
          in: query
          required: true
          description: Search query (name or email, minimum 2 characters)
          schema:
            type: string
            minLength: 2
        - name: job_id
          in: query
          description: Limit search to a specific job
          schema:
            type: string
            format: uuid
        - name: limit
          in: query
          description: Maximum number of results (default 20, max 50)
          schema:
            type: integer
            minimum: 1
            maximum: 50
            default: 20
      responses:
        "200":
          description: Search results
          content:
            application/json:
              schema:
                type: object
                properties:
                  applications:
                    type: array
                    items:
                      $ref: "#/components/schemas/Application"
                  query:
                    type: string
                  count:
                    type: integer
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"

  /candidates/{candidateId}:
    patch:
      operationId: updateCandidate
      summary: Update candidate
      description: Update a candidate's profile fields. Adding a LinkedIn URL triggers AI profile processing.
      tags:
        - Candidates
      parameters:
        - name: candidateId
          in: path
          required: true
          description: The candidate ID
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                full_name:
                  type: string
                primary_email:
                  type: string
                  format: email
                linkedin_url:
                  type: string
                  format: uri
                website_url:
                  type: string
                  format: uri
                github_url:
                  type: string
                  format: uri
                years_experience:
                  type: integer
                  nullable: true
                university:
                  type: string
            example:
              linkedin_url: "https://linkedin.com/in/janesmith"
              years_experience: 8
      responses:
        "200":
          description: Candidate updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  candidate:
                    $ref: "#/components/schemas/CandidateDetail"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

  /sourcing/searches:
    get:
      operationId: listSourcingSearches
      summary: List sourcing searches
      description: List all sourcing searches for your organization.
      tags:
        - Sourcing
      responses:
        "200":
          description: List of sourcing searches
          content:
            application/json:
              schema:
                type: object
                properties:
                  searches:
                    type: array
                    items:
                      $ref: "#/components/schemas/SourcingSearch"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"

    post:
      operationId: createSourcingSearch
      summary: Create sourcing search
      description: Create and run a new sourcing search. Searches for candidates matching the query, enriches profiles with AI, and saves them as prospects.
      tags:
        - Sourcing
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - query
              properties:
                query:
                  type: string
                  description: "Natural language search query, e.g. 'senior React engineers in Austin who worked at startups'"
            example:
              query: "senior React engineers in Austin who worked at startups"
      responses:
        "201":
          description: Search created and executed
          content:
            application/json:
              schema:
                type: object
                properties:
                  search:
                    $ref: "#/components/schemas/SourcingSearch"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/InternalError"

  /sourcing/searches/{searchId}:
    get:
      operationId: getSourcingSearch
      summary: Get sourcing search
      description: Get details about a specific sourcing search.
      tags:
        - Sourcing
      parameters:
        - name: searchId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Search details
          content:
            application/json:
              schema:
                type: object
                properties:
                  search:
                    $ref: "#/components/schemas/SourcingSearch"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

    delete:
      operationId: deleteSourcingSearch
      summary: Delete sourcing search
      description: Delete a sourcing search and all its associated prospects.
      tags:
        - Sourcing
      parameters:
        - name: searchId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Search deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  deleted:
                    type: boolean
              example:
                deleted: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

  /sourcing/searches/{searchId}/prospects:
    get:
      operationId: listSearchProspects
      summary: List search prospects
      description: List all prospects from a specific sourcing search.
      tags:
        - Sourcing
      parameters:
        - name: searchId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: List of prospects
          content:
            application/json:
              schema:
                type: object
                properties:
                  prospects:
                    type: array
                    items:
                      $ref: "#/components/schemas/Prospect"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

  /sourcing/prospects/{prospectId}:
    get:
      operationId: getProspect
      summary: Get prospect
      description: Get full details about a specific sourcing prospect.
      tags:
        - Sourcing
      parameters:
        - name: prospectId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Prospect details
          content:
            application/json:
              schema:
                type: object
                properties:
                  prospect:
                    $ref: "#/components/schemas/Prospect"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

  /sourcing/prospects/{prospectId}/convert:
    post:
      operationId: convertProspect
      summary: Convert prospect to candidate
      description: Convert a sourcing prospect into a candidate with an application on a specific job. Places them in the first post-Inbox stage by default.
      tags:
        - Sourcing
      parameters:
        - name: prospectId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - job_id
              properties:
                job_id:
                  type: string
                  format: uuid
                  description: The job to create the application for
                stage_id:
                  type: string
                  format: uuid
                  description: Optional specific stage to place the candidate in
            example:
              job_id: "550e8400-e29b-41d4-a716-446655440000"
      responses:
        "200":
          description: Prospect converted
          content:
            application/json:
              schema:
                type: object
                properties:
                  conversion:
                    type: object
                    properties:
                      application_id:
                        type: string
                        format: uuid
                      candidate_id:
                        type: string
                        format: uuid
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

  /sourcing/prospects/{prospectId}/email:
    patch:
      operationId: updateProspectEmail
      summary: Update prospect email
      description: Manually set or update a prospect's email address.
      tags:
        - Sourcing
      parameters:
        - name: prospectId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - email
              properties:
                email:
                  type: string
                  format: email
                  description: The email address to save
            example:
              email: "jane@example.com"
      responses:
        "200":
          description: Email updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  prospect:
                    $ref: "#/components/schemas/Prospect"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

  /sourcing/prospects/{prospectId}/reveal-email:
    post:
      operationId: revealProspectEmail
      summary: Reveal prospect email
      description: Reveal a prospect's email address using email lookup services. Uses one email reveal credit from your plan.
      tags:
        - Sourcing
      parameters:
        - name: prospectId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Email reveal result
          content:
            application/json:
              schema:
                type: object
                properties:
                  email:
                    type: string
                    format: email
                    nullable: true
                  status:
                    type: string
                  error:
                    type: string
                    nullable: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

  /sourcing/prospects/{prospectId}/pipeline-stage:
    patch:
      operationId: updateProspectPipelineStage
      summary: Update prospect pipeline stage
      description: Update a shortlisted prospect's pipeline stage.
      tags:
        - Sourcing
      parameters:
        - name: prospectId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - pipeline_stage
              properties:
                pipeline_stage:
                  type: string
                  enum: [not_contacted, contacted, interested, not_interested]
            example:
              pipeline_stage: "contacted"
      responses:
        "200":
          description: Pipeline stage updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  prospect:
                    $ref: "#/components/schemas/Prospect"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

  /sourcing/prospects/{prospectId}/shortlist:
    post:
      operationId: shortlistProspect
      summary: Shortlist prospect
      description: Add a prospect to the shortlist. Changes status from `discovered` to `shortlisted`.
      tags:
        - Sourcing
      parameters:
        - name: prospectId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Prospect shortlisted
          content:
            application/json:
              schema:
                type: object
                properties:
                  prospect:
                    $ref: "#/components/schemas/Prospect"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

    delete:
      operationId: unshortlistProspect
      summary: Remove from shortlist
      description: Remove a prospect from the shortlist. Changes status back to `discovered`.
      tags:
        - Sourcing
      parameters:
        - name: prospectId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Prospect removed from shortlist
          content:
            application/json:
              schema:
                type: object
                properties:
                  prospect:
                    $ref: "#/components/schemas/Prospect"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

  /sourcing/shortlist:
    get:
      operationId: listShortlistedProspects
      summary: List shortlisted prospects
      description: List all shortlisted prospects across all searches.
      tags:
        - Sourcing
      responses:
        "200":
          description: Shortlisted prospects
          content:
            application/json:
              schema:
                type: object
                properties:
                  prospects:
                    type: array
                    items:
                      $ref: "#/components/schemas/Prospect"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"

  /sourcing/shortlist/export:
    get:
      operationId: exportShortlist
      summary: Export shortlist
      description: Export all shortlisted prospects with key fields for external use.
      tags:
        - Sourcing
      responses:
        "200":
          description: Exported prospect data
          content:
            application/json:
              schema:
                type: object
                properties:
                  prospects:
                    type: array
                    items:
                      $ref: "#/components/schemas/ProspectExport"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: API key from Settings > API Keys

  schemas:
    Job:
      type: object
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
        status:
          type: string
          enum: [published, draft, closed]
        department:
          type: string
          nullable: true
        location:
          type: string
          nullable: true
        employment_type:
          type: string
          nullable: true
        public_slug:
          type: string
          nullable: true
        created_at:
          type: string
          format: date-time
        stages:
          type: array
          items:
            $ref: "#/components/schemas/Stage"

    JobDetail:
      allOf:
        - $ref: "#/components/schemas/Job"
        - type: object
          properties:
            description_md:
              type: string
              nullable: true
            show_salary:
              type: boolean
            salary_currency:
              type: string
              nullable: true
            salary_timeframe:
              type: string
              nullable: true
            salary_min:
              type: number
              nullable: true
            salary_max:
              type: number
              nullable: true
            stages:
              type: array
              items:
                allOf:
                  - $ref: "#/components/schemas/Stage"
                  - type: object
                    properties:
                      application_count:
                        type: integer

    Stage:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        order_index:
          type: integer

    Candidate:
      type: object
      properties:
        id:
          type: string
          format: uuid
        full_name:
          type: string
        primary_email:
          type: string
          format: email
          nullable: true
        linkedin_url:
          type: string
          format: uri
          nullable: true

    CandidateDetail:
      allOf:
        - $ref: "#/components/schemas/Candidate"
        - type: object
          properties:
            website_url:
              type: string
              nullable: true
            github_url:
              type: string
              nullable: true
            years_experience:
              type: integer
              nullable: true
            university:
              type: string
              nullable: true

    Application:
      type: object
      properties:
        id:
          type: string
          format: uuid
        stage_id:
          type: string
          format: uuid
        source_type:
          type: string
        created_at:
          type: string
          format: date-time
        candidate:
          $ref: "#/components/schemas/Candidate"
        stage:
          $ref: "#/components/schemas/Stage"
        job:
          type: object
          properties:
            id:
              type: string
              format: uuid
            title:
              type: string

    ApplicationDetail:
      allOf:
        - $ref: "#/components/schemas/Application"
        - type: object
          properties:
            candidate:
              $ref: "#/components/schemas/CandidateDetail"
            job:
              type: object
              properties:
                id:
                  type: string
                title:
                  type: string
                department:
                  type: string
                  nullable: true
                location:
                  type: string
                  nullable: true
            ai_profiles:
              type: array
              items:
                type: object
                properties:
                  status:
                    type: string
                  summary:
                    type: string
                    nullable: true
                  extracted_json:
                    type: object
                    nullable: true
            notes:
              type: array
              items:
                $ref: "#/components/schemas/Note"
            interview_feedback:
              type: array
              items:
                $ref: "#/components/schemas/Feedback"
            attachments:
              type: array
              items:
                type: object
                properties:
                  id:
                    type: string
                  file_type:
                    type: string
                  original_filename:
                    type: string
            application_answers:
              type: array
              items:
                type: object
                properties:
                  id:
                    type: string
                  question_text:
                    type: string
                  answer_text:
                    type: string

    Note:
      type: object
      properties:
        id:
          type: string
          format: uuid
        body:
          type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
          nullable: true

    Feedback:
      type: object
      properties:
        id:
          type: string
          format: uuid
        interview_type:
          type: string
        rating:
          type: string
          enum: [strong_yes, yes, no, strong_no]
          nullable: true
        body:
          type: string
        created_at:
          type: string
          format: date-time
        submitted_at:
          type: string
          format: date-time
          nullable: true

    Pagination:
      type: object
      properties:
        limit:
          type: integer
        offset:
          type: integer
        has_more:
          type: boolean

    SourcingSearch:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
          nullable: true
        query:
          type: string
        parsed_filters:
          type: object
          nullable: true
        result_count:
          type: integer
        enrichment_status:
          type: string
          nullable: true
        created_at:
          type: string
          format: date-time

    Prospect:
      type: object
      properties:
        id:
          type: string
          format: uuid
        full_name:
          type: string
        current_title:
          type: string
          nullable: true
        current_company:
          type: string
          nullable: true
        linkedin_url:
          type: string
          format: uri
          nullable: true
        email:
          type: string
          format: email
          nullable: true
        location:
          type: string
          nullable: true
        years_experience:
          type: integer
          nullable: true
        summary:
          type: string
          nullable: true
        status:
          type: string
          enum: [discovered, shortlisted, converted]
        pipeline_stage:
          type: string
          enum: [not_contacted, contacted, interested, not_interested]
          nullable: true
        created_at:
          type: string
          format: date-time

    ProspectExport:
      type: object
      properties:
        full_name:
          type: string
        current_title:
          type: string
          nullable: true
        current_company:
          type: string
          nullable: true
        linkedin_url:
          type: string
          nullable: true
        location:
          type: string
          nullable: true
        years_experience:
          type: integer
          nullable: true
        summary:
          type: string
          nullable: true
        pipeline_stage:
          type: string
          nullable: true

    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              enum:
                - invalid_request
                - unauthorized
                - forbidden
                - not_found
                - rate_limit_exceeded
                - internal_error
            message:
              type: string
            resolution:
              type: string
              description: A concrete next step an agent can take to resolve the error
            details:
              type: object
          required: [code, message, resolution]
      required: [error]

  responses:
    BadRequest:
      description: Bad request - invalid or missing parameters
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error:
              code: "invalid_request"
              message: "job_id is required"
              resolution: "Correct the request using the Loam OpenAPI specification at /openapi.yaml, then retry."

    Unauthorized:
      description: Unauthorized - missing or invalid API key
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error:
              code: "unauthorized"
              message: "Invalid or missing API key"
              resolution: "Send a valid Bearer token in the Authorization header. See /developers for authentication documentation."

    Forbidden:
      description: Forbidden - insufficient permissions
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error:
              code: "forbidden"
              message: "Missing required scope: write"
              resolution: "Use credentials with the required scope or ask a Loam organization admin to grant access."

    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error:
              code: "not_found"
              message: "Application not found"
              resolution: "Verify the resource identifier and endpoint path against /openapi.yaml, then retry."

    RateLimitExceeded:
      description: Rate limit exceeded
      headers:
        Retry-After:
          schema:
            type: integer
          description: Seconds until rate limit resets
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error:
              code: "rate_limit_exceeded"
              message: "Rate limit exceeded. Try again in 42 seconds."
              resolution: "Wait for the indicated retry period before sending the request again."
              details:
                retry_after: 42

    InternalError:
      description: Internal server error
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error:
              code: "internal_error"
              message: "An internal error occurred"
              resolution: "Retry later. If the error persists, contact Loam support with the request details."
