openapi: 3.1.0
info:
  title: Flyt AI Platform API
  version: "1.0.0"
  description: |
    # Flyt AI Platform

    # Available Services

    ## Field Extractions (Post-Registration Documents)
    Extract structured fields from post-registration documents using AI-powered analysis. Upload PDF documents and receive structured JSON output with extracted field values.

    **Current Use Case:**
    - Flyt PPT post-registration

    **Field Schema:**
    The service extracts fields specific to Flyt PPT post-registration, including sender information,
    regarding party details, document categorization, and case type classification.

    ### Endpoints
    - `POST /api/v1/field-extractions` - Create a new extraction job and get upload URL
    - `GET /api/v1/field-extractions/{job_id}` - Get extraction result for a job

    ### Limitations
    - Maximum document size: 10 MB
    - Supported document format: PDF

    ### Data Extraction Quality
    The AI service employs advanced techniques to ensure high-quality data extraction:
    - **No Hallucination**: Only extracts information explicitly present in documents
    - **Optional Fields**: All extracted fields may be empty when information is not clearly available
    - **Conservative Approach**: Prefers empty fields over potentially incorrect data

    ### Data Retention and Privacy
    The service implements automated data cleanup to protect sensitive information:

    #### Retention Periods (Environment-specific)
    - **Production**: 7 days after processing completion
    - **Development/Staging**: 30 days after processing completion

    #### Cleanup Behavior
    - **Uploaded Documents**: PDF files are completely deleted from storage
    - **Processing Results**: Sensitive extracted data is removed and replaced with a deletion status message
    - **Job History**: Job records are preserved for audit purposes but without sensitive data

    #### Impact on API Responses

    After data cleanup occurs:
    - Job status changes to `"expired"`
    - Extracted fields will be empty (`{}`)
    - The response message will be "Data deleted due to retention policy"
    - Upload URLs and processing metadata are removed

    Plan your integration accordingly to retrieve results within the retention window.

    ## Audio Transcriptions
    Convert audio files to text using advanced speech-to-text technology powered by ElevenLabs. Upload audio files and receive accurate transcriptions in SRT format with AI-generated summaries.

    **Supported Formats:**
    - MP3 (.mp3)
    - AAC (.aac)
    - M4A (.m4a)
    - OGG (.ogg)

    **Limitations:**
    - Maximum audio duration: 180 minutes (3 hours)
    - Maximum file size: 200 MB
    - Languages: Norwegian Bokmål, English (multilingual model)

    ### Endpoints
    - `POST /api/v1/transcriptions` - Create a new transcription job and get upload URL
    - `GET /api/v1/transcriptions/{job_id}` - Get transcription result for a job
    - `PUT /api/v1/transcriptions/summary` - Re-generate the transcription summary

    ### Summary Sections

    Transcript summaries are structured into customizable sections to help organize key information:

    **Default Sections (Norwegian):**
    When no custom sections are specified, the service uses three default sections:
    1. **Hovedpunkter** - Main topics and themes from the conversation
    2. **Handlingspunkter** - Concrete action items, tasks, and next steps
    3. **Avgjørelser** - Key decisions made during the conversation

    **Custom Sections:**
    Integrators can define custom summary sections by providing an array of section definitions, each with:
    - `title`: Section heading
    - `description`: Brief instruction on what to include in this section

    **Validation and Security:**
    - All input is sanitized to prevent prompt injection (Unicode normalization, control character removal)
    - Suspicious patterns are logged but not blocked (user already has full transcript access)
    - See transcription endpoint descriptions and schemas for detailed validation rules

    **Response Format:**
    Summary responses include both:
    - `transcript_summary`: Complete summary text (all sections joined) for backward compatibility
    - `summary_sections`: Array of section objects with `title` and `content` fields


    ### Job Lifecycle

    **Transcription Jobs (`POST /api/v1/transcriptions`):**
    - Creates a unique `job_id` for tracking the asynchronous transcription process
    - Use `GET /api/v1/transcriptions/{job_id}` to poll for results
    - Job IDs are specific to transcription operations only
    - Job IDs expire after data retention period (see Data Retention section)

    **Summary (Re)generation (`PUT /api/v1/transcriptions/summary`):**
    - Stateless, synchronous operation with immediate response
    - Does NOT use or create job IDs
    - Cannot reuse job IDs from transcription operations
    - Each request is independent and requires the full transcript text
    - Returns summary directly in response (no polling required)
    - Supports optional custom section definitions (see Summary Sections above)

    ### Multi-tenancy
    Transcription jobs are isolated by `client_id`. When retrieving results, the service validates that the requesting client owns the job, returning 403 Forbidden for unauthorized access attempts.

    ### Data Retention and Privacy
    The service implements automated data cleanup to protect sensitive information:

    #### Retention Periods (Environment-specific)
    - **Production**: 7 days after processing completion
    - **Development/Staging**: 30 days after processing completion

    #### Cleanup Behavior
    - **Uploaded Audio Files**: Audio files are completely deleted from storage
    - **Processing Results**: Transcription results (SRT format and summaries) are removed and replaced with a deletion status message
    - **Job History**: Job records are preserved for audit purposes but without sensitive data

    #### Impact on API Responses

    After data cleanup occurs:
    - Job status changes to `"expired"`
    - Transcript SRT will be `null`
    - Transcript summary will be "Data deleted due to retention policy"
    - Upload URLs and metadata are removed

    Plan your integration accordingly to retrieve results within the retention window.

    ## Future Services

    ### Document Summaries (Coming Soon)
    Generate AI-powered summaries of documents.

    ### Case Summaries (Coming Soon)
    Generate comprehensive case summaries from multiple documents and metadata.

    # API Versioning

    This API uses URL path versioning (e.g., `/api/v1/field-extractions`). Breaking changes will be introduced in new versions (v2, v3, etc.) while maintaining backward compatibility for existing versions for a deprecation period.

    **Versioning Policy:**
    - Minor updates (bug fixes, new optional fields) do not require version changes
    - Breaking changes (removed fields, changed behavior) require new API version

tags:
  - name: field-extractions
    description: AI-powered field extraction from documents
  - name: summaries
    description: Document and case summarization services (coming soon)
  - name: transcriptions
    description: Audio transcription services
  - name: health
    description: Health and status endpoints

paths:
  /api/health:
    get:
      summary: Check API health status
      tags:
        - health
      description: |
        Returns the health status of the API. Use this endpoint to verify that
        the service is running and accessible. The response includes the application
        version (git branch and commit hash) for deployment tracking.
      responses:
        "200":
          description: Service is healthy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"
              example:
                message: "API is up and running 👍"
                app_version: "main-a1b2c3d"

  /api/v1/field-extractions:
    post:
      security:
        - BearerAuth: []
      tags:
        - field-extractions
      summary: Create a field extraction job
      description: |
        Creates a new field extraction job and returns a pre-signed URL
        for uploading the document. The URL is valid for 15 minutes and has
        a maximum file size limit of 10 MB.

        Once the document is uploaded to the pre-signed URL, processing begins automatically.
        Use the returned job_id to check the extraction status and retrieve results.

        Note: Documents larger than 10 MB will be rejected during processing.

        **Authentication:**
        This endpoint requires a valid JWT Bearer token in the `Authorization` header.
        Obtain a token using the Visma Connect OAuth 2.0 client credentials flow.
        Example:
        ```http
        Authorization: Bearer <access_token>
        ```
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateJobRequest"
      responses:
        "200":
          description: Job created successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreateJobResponse"
        "400":
          description: Invalid request format
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Unauthorized (missing or invalid token)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Server error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/field-extractions/{job_id}:
    get:
      security:
        - BearerAuth: []
      tags:
        - field-extractions
      summary: Get field extraction result
      description: |
        Returns the extraction result for a job. If processing is not
        yet complete, the response will indicate pending/in-progress status.

        **Response Status Codes:**
        - `200 OK` - Processing complete (success or error)
        - `202 Accepted` - Field extraction in progress or pending
        - `404 Not Found` - Job ID not found

        **Authentication:**
        This endpoint requires a valid JWT Bearer token in the `Authorization` header.
        Obtain a token using the Visma Connect OAuth 2.0 client credentials flow.
        Example:
        ```http
        Authorization: Bearer <access_token>
      parameters:
        - name: job_id
          in: path
          required: true
          schema:
            type: string
          description: The unique identifier for the extraction job
      responses:
        "200":
          description: Processing complete (success or error)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DocumentResponse"
              examples:
                successful_processing:
                  summary: Successful document processing (all fields found)
                  value:
                    status: "success"
                    message: "Document processed successfully"
                    extracted_fields:
                      summary: "Patient referral for specialist evaluation"
                      documentDate: "17.05.2023"
                      postCategory: "Referral"
                      subject: "Specialist evaluation request"
                      senderType: "person"
                      sender:
                        name: "John Doe"
                        nationalIdNumber: "12345678901"
                      regardingPartyType: "person"
                      regardingParty:
                        name: "Jane Smith"
                        nationalIdNumber: "09876543210"
                partial_extraction:
                  summary: Partial extraction (some fields empty when not found)
                  value:
                    status: "success"
                    message: "Document processed successfully"
                    extracted_fields:
                      summary: "Document with limited information available"
                      documentDate: ""
                      postCategory: ""
                      subject: "Service request"
                      senderType: "person"
                      sender:
                        name: "John Doe"
                        nationalIdNumber: ""
                      regardingPartyType: ""
                      regardingParty: null
                expired_data:
                  summary: Data deleted due to retention policy
                  value:
                    status: "expired"
                    message: "Data deleted due to retention policy"
                    extracted_fields: {}
                error_processing:
                  summary: Processing error
                  value:
                    status: "error"
                    message: "Failed to process document"
                    extracted_fields: {}
        "202":
          description: Upload is still pending or field extraction in progress
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DocumentResponse"
              examples:
                pending_upload:
                  summary: Awaiting document upload
                  value:
                    status: "pending_upload"
                    message: "Awaiting document upload"
                    extracted_fields: {}
                in_progress:
                  summary: Field extraction in progress
                  value:
                    status: "in_progress"
                    message: "Field extraction in progress"
                    extracted_fields: {}
        "400":
          description: Invalid job ID
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Unauthorized (missing or invalid token)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Job not found
        "500":
          description: Server error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/transcriptions:
    post:
      security:
        - BearerAuth: []
      tags:
        - transcriptions
      summary: Create a transcription job
      description: |
        Creates a new transcription job and returns a pre-signed URL for uploading
        the audio file. The URL is valid for 15 minutes and has a maximum file
        size limit of 200 MB.

        Once the audio file is uploaded to the pre-signed URL, transcription processing
        begins automatically. Use the returned job_id to check the transcription status
        and retrieve results.

        **Supported Audio Formats:**
        - MP3 (.mp3)
        - AAC (.aac)
        - M4A (.m4a)
        - OGG (.ogg)

        **Limitations:**
        - Maximum audio duration: 180 minutes (3 hours)
        - Maximum file size: 200 MB
        - Languages: Norwegian Bokmål, English (multilingual model)

        **Features:**
        - Speech-to-Text transcription in SRT format with timestamps
        - AI-generated summaries (optional, enabled by default)
        - Audio enhancement option for improved accuracy

        **Uploading the Audio File:**

        After receiving the pre-signed upload URL, perform a PUT request to upload your audio file:

        - **Method:** PUT
        - **Headers:**
          - `x-ms-blob-type: BlockBlob` (required for Azure Blob Storage)
          - `Content-Type: audio/{format}` (required, e.g., audio/mpeg for MP3, audio/aac for AAC, audio/ogg for OGG)

        **Example:**
        ```http
        PUT https://storage.azure.com/transcriptions/job-123.mp3?sas=token...
        x-ms-blob-type: BlockBlob
        Content-Type: audio/mpeg
        ```

        **Authentication:**
        This endpoint requires a valid JWT Bearer token in the `Authorization` header.
        Obtain a token using the Visma Connect OAuth 2.0 client credentials flow.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TranscriptionRequest"
      responses:
        "200":
          description: Job created successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TranscriptionResponse"
        "400":
          description: Invalid request format
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Unauthorized (missing or invalid token)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Server error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/transcriptions/{job_id}:
    get:
      security:
        - BearerAuth: []
      tags:
        - transcriptions
      summary: Get transcription result
      description: |
        Returns the transcription result for a job. If processing is not yet complete,
        the response will indicate pending/in-progress status.

        **Response Status Codes:**
        - `200 OK` - Transcription complete (success or error)
        - `202 Accepted` - Transcription in progress or pending upload
        - `404 Not Found` - Job ID not found
        - `403 Forbidden` - Job belongs to different client (multi-tenancy violation)

        **Multi-tenancy:**
        The service validates that the requesting client (via JWT token) owns the job.

        **Authentication:**
        This endpoint requires a valid JWT Bearer token in the `Authorization` header.
      parameters:
        - name: job_id
          in: path
          required: true
          schema:
            type: string
          description: The unique identifier for the transcription job
      responses:
        "200":
          description: Transcription complete (success or error)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TranscriptionResult"
              examples:
                successful_transcription:
                  summary: Successful transcription with summary
                  value:
                    status: "success"
                    message: "Transcription completed successfully"
                    transcript_srt: |
                      1
                      00:00:00,000 --> 00:00:05,000
                      Hello, this is a sample transcription.

                      2
                      00:00:05,000 --> 00:00:10,000
                      It contains timestamped text segments.
                    transcript_summary: "Hovedpunkter: En kort eksempeltranskripsjon som demonstrerer tidsstemplede tekstsegmenter. Handlingspunkter: Ingen konkrete handlingspunkter identifisert. Avgjørelser: Ingen avgjørelser tatt i denne samtalen."
                    summary_sections:
                      - title: "Hovedpunkter"
                        content: "En kort eksempeltranskripsjon som demonstrerer tidsstemplede tekstsegmenter."
                      - title: "Handlingspunkter"
                        content: "Ingen konkrete handlingspunkter identifisert."
                      - title: "Avgjørelser"
                        content: "Ingen avgjørelser tatt i denne samtalen."
                    metadata:
                      duration: 10.0
                      language: "en"
                      model: "eleven_multilingual_v2"
                expired_data:
                  summary: Data deleted due to retention policy
                  value:
                    status: "expired"
                    message: "Data deleted due to retention policy"
                    transcript_srt: null
                    transcript_summary: "Transcription data has been deleted."
                    metadata: null
                error_processing:
                  summary: Processing error (invalid audio format)
                  value:
                    status: "error"
                    message: "Unsupported audio format '.mp4'. Supported formats: .mp3, .aac, .m4a, .ogg"
                    transcript_srt: null
                    transcript_summary: "Transcription failed due to unsupported audio format."
                    metadata: null
        "202":
          description: Upload pending or transcription in progress
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TranscriptionResult"
              examples:
                pending_upload:
                  summary: Awaiting audio file upload
                  value:
                    status: "pending_upload"
                    message: "Awaiting audio file upload"
                    transcript_srt: null
                    transcript_summary: "Awaiting audio upload and transcription."
                    metadata: null
                transcribing:
                  summary: Transcription in progress
                  value:
                    status: "transcribing"
                    message: "Transcription in progress"
                    transcript_srt: null
                    transcript_summary: "Transcription is being processed."
                    metadata: null
        "400":
          description: Invalid job ID
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Unauthorized (missing or invalid token)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden (job belongs to different client)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Job not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Server error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/transcriptions/summary:
    put:
      security:
        - BearerAuth: []
      tags:
        - transcriptions
      summary: Re-generate transcription summary
      description: |
        Re-generates an AI-powered summary for an existing transcript. This endpoint
        allows you to regenerate the summary with additional context or metadata,
        or to improve upon an existing summary.

        **Use Cases:**
        - Generate summary with additional context metadata
        - Improve an existing summary by providing it as reference
        - Create summary for transcript obtained elsewhere
        - Define custom summary sections tailored to your use case

        **Summary Sections:**
        - **Default behavior**: When `summary_sections` is not provided, uses default Norwegian sections (Hovedpunkter, Handlingspunkter, Avgjørelser)
        - **Custom sections**: Provide an array of section definitions to customize the summary structure
        - Each section definition includes a `title` and `description` to guide AI generation
        - All custom text is sanitized to prevent prompt injection

        **Response Format:**
        - Returns both `transcript_summary` (joined text) for backward compatibility
        - And `summary_sections` array with structured section content

        **Important:** This endpoint does NOT use or create job IDs. It works independently
        of the transcription job workflow as a stateless summary generation service.
        You cannot reuse job IDs from `POST /api/v1/transcriptions` with this endpoint.
        Each request is processed immediately and returns the summary in the response.

        **Authentication:**
        This endpoint requires a valid JWT Bearer token in the `Authorization` header.
        Obtain a token using the Visma Connect OAuth 2.0 client credentials flow.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TranscriptionSummaryRequest"
            examples:
              basic_summary:
                summary: Basic summary generation with default Norwegian sections
                value:
                  transcription: |
                    1
                    00:00:00,000 --> 00:00:05,000
                    Rådgiver: Velkommen, takk for at dere kunne komme.

                    2
                    00:00:05,000 --> 00:00:10,000
                    Mor: Takk for at du tok deg tid.
              with_custom_sections:
                summary: Summary with custom section definitions
                value:
                  transcription: |
                    1
                    00:00:00,000 --> 00:00:05,000
                    Project Manager: Let's review the quarterly goals.

                    2
                    00:00:05,000 --> 00:00:10,000
                    Team Lead: We completed 85% of planned features.
                  summary_sections:
                    - title: "Key Discussion Points"
                      description: "Main topics and themes discussed during the meeting"
                    - title: "Action Items"
                      description: "Concrete tasks, decisions, and next steps assigned"
                    - title: "Risks and Blockers"
                      description: "Any challenges, concerns, or impediments mentioned"
              with_metadata:
                summary: Summary with additional context metadata
                value:
                  transcription: |
                    1
                    00:00:00,000 --> 00:00:05,000
                    This is a conversation transcript.
                  metadata: '{"context": "meeting notes", "subject": "Project Update", "participants": ["Alice", "Bob"]}'
              with_existing_summary:
                summary: Improve existing summary
                value:
                  transcription: |
                    1
                    00:00:00,000 --> 00:00:05,000
                    Full transcript text here.
                  existing_summary: "Previous brief summary that needs improvement"
                  metadata: '{"context": "school meeting"}'
              with_user_instructions:
                summary: Summary with user instructions
                value:
                  transcription: |
                    1
                    00:00:00,000 --> 00:00:05,000
                    Full transcript text here.
                  user_instructions: "Focus on action items and decisions made"
      responses:
        "200":
          description: Summary generated successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TranscriptionSummaryResponse"
              examples:
                successful_default_sections:
                  summary: Summary with default Norwegian sections
                  value:
                    status: "success"
                    transcript_summary: "Hovedpunkter: Samtalen omhandlet prosjektmål og fremdrift. Handlingspunkter: 1. Fullføre rapport innen fredag. 2. Planlegge oppfølgingsmøte. Avgjørelser: Godkjent budsjett for Q2."
                    summary_sections:
                      - title: "Hovedpunkter"
                        content: "Samtalen omhandlet prosjektmål og fremdrift. Teamet diskuterte status på gjeldende oppgaver og neste steg."
                      - title: "Handlingspunkter"
                        content: "1. Fullføre rapport innen fredag. 2. Planlegge oppfølgingsmøte neste uke. 3. Dele oppdatert tidsplan med teamet."
                      - title: "Avgjørelser"
                        content: "Godkjent budsjett for Q2. Besluttet å utvide teamet med en ny utvikler."
                successful_custom_sections:
                  summary: Summary with custom sections
                  value:
                    status: "success"
                    transcript_summary: "Key Discussion Points: The meeting covered quarterly goals and feature completion at 85%. Action Items: Review remaining 15% of planned features. Risks and Blockers: Resource constraints may impact Q2 timeline."
                    summary_sections:
                      - title: "Key Discussion Points"
                        content: "The meeting covered quarterly goals and feature completion at 85%. Team discussed client feedback and upcoming priorities."
                      - title: "Action Items"
                        content: "Review remaining 15% of planned features. Schedule client demo for next week. Update project roadmap."
                      - title: "Risks and Blockers"
                        content: "Resource constraints may impact Q2 timeline. Dependency on external API integration poses potential delays."
                generation_error:
                  summary: Summary generation failed
                  value:
                    status: "error"
                    transcript_summary: ""
        "400":
          description: Invalid request format or missing required fields
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Unauthorized (missing or invalid token)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Server error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
  schemas:
    CreateJobRequest:
      $ref: "./schemas/create-job-request.yaml"
    CreateJobResponse:
      $ref: "./schemas/create_job_response.yaml"
    DocumentResponse:
      $ref: "./schemas/document-response.yaml"
    ErrorResponse:
      $ref: "./schemas/error-response.yaml"
    HealthResponse:
      $ref: "./schemas/health-response.yaml"
    SummarySection:
      $ref: "./schemas/summary-section.yaml"
    SummarySectionOutput:
      $ref: "./schemas/summary-section-output.yaml"
    TranscriptionRequest:
      $ref: "./schemas/transcription-request.yaml"
    TranscriptionResponse:
      $ref: "./schemas/transcription-response.yaml"
    TranscriptionResult:
      $ref: "./schemas/transcription-result.yaml"
    TranscriptionSummaryRequest:
      $ref: "./schemas/transcription-summary-request.yaml"
    TranscriptionSummaryResponse:
      $ref: "./schemas/transcription-summary-response.yaml"
