openapi: 3.1.0
info:
  title: Distill.cv API
  version: "1.0"
  description: |
    REST API for programmatic access to the Distill.cv CV processing pipeline.

    ## Authentication

    All requests must include an API key in the `Authorization` header:

    ```
    Authorization: Bearer <api_key>
    ```

    API keys are created from the Distill dashboard under **Settings → API Keys**.
    The full key value is shown only once at creation time — store it securely.

  contact:
    email: support@distill.cv
  license:
    name: Proprietary

servers:
  - url: https://api.distill.cv/v1
    description: Production

security:
  - ApiKeyAuth: []

components:
  securitySchemes:
    ApiKeyAuth:
      type: http
      scheme: bearer
      description: Bearer token using a Distill API key (`<api_key>`)

  schemas:
    Distillation:
      type: object
      properties:
        id:
          type: string
          description: Unique identifier for the distillation job.
          example: V1StGXR8_Z5jdHi6B3Ui4
        status:
          type: string
          enum:
            - INITIATED
            - UPLOADED
            - PROCESSING
            - EXTRACTING
            - EXTRACTED
            - DISTILLING
            - GENERATING
            - PROCESSED
            - FAILED
          description: |
            Current pipeline status. Poll `GET /distillations/{id}` until
            `status` is `PROCESSED` before requesting the download URL.
        originalFileName:
          type: string
          example: john_doe_CV.pdf
        mimeType:
          type: string
          example: application/pdf
        sizeBytes:
          type: integer
          example: 245120
        options:
          $ref: "#/components/schemas/DistillationOptions"
        createdAt:
          type: integer
          description: Unix timestamp (ms) of job creation.
          example: 1716300000000
        lastUpdatedAt:
          type: integer
          description: Unix timestamp (ms) of the most recent status change.
          example: 1716300045000

    DistillationOptions:
      type: object
      description: |
        Anonymization and formatting options applied during distillation.

        Options omitted from the object are treated as `false`. The defaults
        below reflect the Distill dashboard defaults — copy them to replicate
        the out-of-the-box blind-screening preset:

        ```json
        {
          "removeName": true,
          "removeContact": true,
          "removeGender": true,
          "removeAge": true
        }
        ```
      properties:
        removeName:
          type: boolean
          default: true
          description: >
            Replace candidate name with a neutral label ("the candidate") throughout
            the document, including inline mentions in narrative text. Also clears the
            title field.
        removeContact:
          type: boolean
          default: true
          description: >
            Strip email address, phone number, and social profile URLs (LinkedIn,
            GitHub, etc.). Also redacts contact details embedded in free-text sections.
        removeLocation:
          type: boolean
          default: false
          description: >
            Remove street address, city, and postcode from the personal section and
            the location field on every job in the work history.
        removeGender:
          type: boolean
          default: true
          description: >
            Strip pronouns and gendered salutations (Mr./Ms./Mrs./Mx. etc.).
            Replaces he/she/him/her with "they" throughout narrative text.
        removeAge:
          type: boolean
          default: true
          description: >
            Remove birth year and graduation years from education entries to
            prevent age bias.
        removeNationality:
          type: boolean
          default: false
          description: Remove citizenship, nationality, and work-authorisation references.
        anonymizeSchools:
          type: boolean
          default: false
          description: >
            Replace school names with neutral labels to reduce prestige bias
            (e.g. "Accredited University", "Accredited College").
        anonymizeCompanies:
          type: boolean
          default: false
          description: >
            Replace company names with neutral labels to reduce brand bias
            (e.g. "Mid-size Technology Company").


    Error:
      type: object
      required:
        - message
      properties:
        message:
          type: string
          example: Distillation not yet processed

paths:
  /distillations:
    post:
      operationId: createDistillation
      summary: Create a distillation job
      description: |
        Submits a CV for processing. The file is sent as a base64-encoded
        string in the JSON body — no second upload call needed.

        The pipeline starts immediately after this call returns. Poll
        `GET /distillations/{id}` until `status` is `PROCESSED`, then call
        `GET /distillations/{id}/download` to retrieve the result.

        **File size:** the raw file must be ≤ 7.5 MB (base64 encoding adds ~33%,
        capping the total JSON body at the 10 MB API Gateway limit).
        CVs are typically 50 KB–3 MB.

        ### Example (curl)
        ```bash
        curl -s -X POST https://api.distill.cv/v1/distillations \
          -H "Authorization: Bearer $DISTILL_API_KEY" \
          -H "Content-Type: application/json" \
          -d "{
            \"filename\": \"CV.pdf\",
            \"content\": \"$(base64 < CV.pdf)\",
            \"options\": { \"removeName\": true }
          }"
        ```

        ### Example (Python)
        ```python
        import base64, requests

        with open("CV.pdf", "rb") as f:
            content = base64.b64encode(f.read()).decode()

        resp = requests.post(
            "https://api.distill.cv/v1/distillations",
            headers={"Authorization": f"Bearer {api_key}"},
            json={"filename": "CV.pdf", "content": content}
        )
        job_id = resp.json()["id"]
        ```
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - content
              properties:
                content:
                  type: string
                  format: byte
                  description: Base64-encoded file content (standard encoding, no line breaks required).
                  example: JVBERi0xLjQKJeLjz9MKMyAwIG9iag==
                filename:
                  type: string
                  description: |
                    Original filename including extension (`.pdf`, `.docx`, `.jpg`, `.jpeg`, `.png`).
                    Used to infer `mimeType` when `mimeType` is not supplied.
                  example: john_doe_CV.pdf
                mimeType:
                  type: string
                  description: |
                    MIME type of the file. Required when `filename` is absent or has
                    no recognised extension.
                  enum:
                    - application/pdf
                    - application/vnd.openxmlformats-officedocument.wordprocessingml.document
                    - image/jpeg
                    - image/png
                  example: application/pdf
                options:
                  $ref: "#/components/schemas/DistillationOptions"
      responses:
        "201":
          description: Job accepted. Pipeline has started; poll status to track progress.
          content:
            application/json:
              schema:
                type: object
                required:
                  - id
                  - status
                properties:
                  id:
                    type: string
                    description: Distillation job ID. Use this to poll status and download the result.
                    example: V1StGXR8_Z5jdHi6B3Ui4
                  status:
                    type: string
                    enum: [INITIATED]
        "400":
          description: Invalid request body.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /distillations/{distillationId}:
    get:
      operationId: getDistillation
      summary: Get distillation status
      description: |
        Returns the current status and metadata of a distillation job.

        Poll this endpoint until `status` is `PROCESSED` before requesting
        the download URL. Typical processing time is 15–60 seconds.

        Recommended polling interval: 5 seconds.
      parameters:
        - name: distillationId
          in: path
          required: true
          schema:
            type: string
          example: V1StGXR8_Z5jdHi6B3Ui4
      responses:
        "200":
          description: Distillation found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Distillation"
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "403":
          description: Distillation belongs to a different organization.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Distillation not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /distillations/{distillationId}/download:
    get:
      operationId: downloadDistillation
      summary: Get download URL
      description: |
        Returns a short-lived (5 minute) signed URL for the processed `.docx` output.

        Only available when `status` is `PROCESSED`. Returns `409` for all other statuses.
      parameters:
        - name: distillationId
          in: path
          required: true
          schema:
            type: string
          example: V1StGXR8_Z5jdHi6B3Ui4
      responses:
        "200":
          description: Signed download URL.
          content:
            application/json:
              schema:
                type: object
                required:
                  - url
                  - expiresInSeconds
                properties:
                  url:
                    type: string
                    format: uri
                    description: Pre-signed S3 URL for the `.docx` file. Valid for 5 minutes.
                  expiresInSeconds:
                    type: integer
                    example: 300
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "403":
          description: Distillation belongs to a different organization.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Distillation not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "409":
          description: Distillation not yet processed.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/Error"
                  - type: object
                    properties:
                      status:
                        type: string
                        description: Current pipeline status.

  /distillations/{distillationId}/restart:
    post:
      operationId: restartDistillation
      summary: Re-process with different options
      description: |
        Re-runs the distill + generate steps using updated `options`.
        The original LLM extraction is reused (no additional AI cost).

        Requires the job to have completed its initial extraction (`status` was
        at least `EXTRACTED` at some point). Returns `409` if extraction has not
        yet finished.

        On success the job transitions back to `DISTILLING` and you should
        CV polling `GET /distillations/{id}`.
      parameters:
        - name: distillationId
          in: path
          required: true
          schema:
            type: string
          example: V1StGXR8_Z5jdHi6B3Ui4
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                options:
                  $ref: "#/components/schemas/DistillationOptions"
      responses:
        "200":
          description: Restart accepted.
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                  status:
                    type: string
                    enum: [DISTILLING]
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "403":
          description: Distillation belongs to a different organization.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Distillation not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "409":
          description: Initial extraction not yet complete.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
