> ## Documentation Index
> Fetch the complete documentation index at: https://docs.linqalpha.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Get Upload URL

> Returns a presigned S3 PUT URL for direct file upload. Lets clients upload files without managing AWS credentials or staging buckets — the server signs a single-use URL bound to a server-generated S3 object key, the client PUTs the file body to that URL, then registers the upload via `POST /v1/sources` with the returned `file_key`.

**Why this exists:** `POST /v1/sources` requires a `file_key` for an object that already exists in our backing storage. Customers without their own staging bucket use this endpoint to obtain an upload URL pointed at the canonical destination directly, eliminating any client-side AWS setup.

**Important — Content-Type binding:** The presigned URL is signed with `Content-Type` baked into the signature. The client **MUST** send a matching `Content-Type` header on the PUT request, otherwise S3 rejects with HTTP 403 (`SignatureDoesNotMatch`). The expected value is returned in the `content_type` field of this response — pass it back as the `Content-Type` header on your PUT.

**Workflow:**
1. Create a source batch via `POST /v1/source_batches` (returns `source_batch_id`)
2. Call this endpoint with `name`, `source_type`, `source_batch_id` (returns `upload_url`, `file_key`, `content_type`, `expires_in`)
3. PUT the file to `upload_url` with `Content-Type: <content_type>` header
4. Register the source via `POST /v1/sources` with `file_key` from step 2
5. Poll `GET /v1/sources/{source_id}` until `status: "success"`
6. Reference `source_batch_id` in chat/search requests

**Constraints:**
- Supported `source_type`: `pdf`, `docx`, `xlsx`, `pptx`, `doc`, `txt`
- Maximum file size enforced at registration step: 20MB
- Maximum 10 active sources (status: `processing` or `success`) per source_batch — `failed`/`deleted` sources do not count toward the limit (enforced when generating the URL)
- URL expires after 10 minutes (`expires_in: 600`)
- File is validated at registration time (`POST /v1/sources`); a wrong-content upload will be accepted by S3 but rejected when registering



## OpenAPI

````yaml POST /v1/sources/upload_url
openapi: 3.0.1
info:
  title: LinqAlpha API
  description: >-
    Linq helps finance professionals make informed decisions using
    Retrieval-Augmented Generation (RAG)-enhanced answers. By leveraging
    cutting-edge Large Language Models (LLM) and supplementary technology, Linq
    provides the most optimized responses based on your queries.
  version: 1.0.0
  license:
    name: MIT
servers:
  - url: https://api.linqalpha.com
security:
  - ApiKeyAuth: []
tags:
  - name: Search
    description: Search and generate responses
  - name: Data
    description: Data retrieval and mapping
  - name: Feedback
    description: Conversation feedback
  - name: RMS
    description: Research Management System
  - name: Source Management
    description: Source batch and file management
  - name: MCP
    description: >-
      LinqAlpha MCP — Financial data tools for AI assistants via Model Context
      Protocol
  - name: Connectors
    description: Customer Connectors — customer-owned MCP connector management
  - name: Briefing
    description: Briefing Agent — automated market briefings with scheduling and delivery
  - name: Status
    description: Sync status — check organization, document, and container sync progress
paths:
  /v1/sources/upload_url:
    post:
      tags:
        - Source Management
      summary: Get Upload URL
      description: >-
        Returns a presigned S3 PUT URL for direct file upload. Lets clients
        upload files without managing AWS credentials or staging buckets — the
        server signs a single-use URL bound to a server-generated S3 object key,
        the client PUTs the file body to that URL, then registers the upload via
        `POST /v1/sources` with the returned `file_key`.


        **Why this exists:** `POST /v1/sources` requires a `file_key` for an
        object that already exists in our backing storage. Customers without
        their own staging bucket use this endpoint to obtain an upload URL
        pointed at the canonical destination directly, eliminating any
        client-side AWS setup.


        **Important — Content-Type binding:** The presigned URL is signed with
        `Content-Type` baked into the signature. The client **MUST** send a
        matching `Content-Type` header on the PUT request, otherwise S3 rejects
        with HTTP 403 (`SignatureDoesNotMatch`). The expected value is returned
        in the `content_type` field of this response — pass it back as the
        `Content-Type` header on your PUT.


        **Workflow:**

        1. Create a source batch via `POST /v1/source_batches` (returns
        `source_batch_id`)

        2. Call this endpoint with `name`, `source_type`, `source_batch_id`
        (returns `upload_url`, `file_key`, `content_type`, `expires_in`)

        3. PUT the file to `upload_url` with `Content-Type: <content_type>`
        header

        4. Register the source via `POST /v1/sources` with `file_key` from step
        2

        5. Poll `GET /v1/sources/{source_id}` until `status: "success"`

        6. Reference `source_batch_id` in chat/search requests


        **Constraints:**

        - Supported `source_type`: `pdf`, `docx`, `xlsx`, `pptx`, `doc`, `txt`

        - Maximum file size enforced at registration step: 20MB

        - Maximum 10 active sources (status: `processing` or `success`) per
        source_batch — `failed`/`deleted` sources do not count toward the limit
        (enforced when generating the URL)

        - URL expires after 10 minutes (`expires_in: 600`)

        - File is validated at registration time (`POST /v1/sources`); a
        wrong-content upload will be accepted by S3 but rejected when
        registering
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UploadUrlRequest'
            example:
              name: Q4_Revenue_Report.pdf
              source_type: pdf
              source_batch_id: 123e4567-e89b-12d3-a456-426614174000
      responses:
        '200':
          description: Presigned upload URL generated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UploadUrlResponse'
              example:
                upload_url: >-
                  https://linq-vertex-documents.s3.amazonaws.com/uploads/1715000000000_a1b2c3d4_Q4_Revenue_Report.pdf/original.pdf?X-Amz-Algorithm=...&X-Amz-Signature=...
                file_key: >-
                  uploads/1715000000000_a1b2c3d4_Q4_Revenue_Report.pdf/original.pdf
                content_type: application/pdf
                expires_in: 600
        '400':
          description: Bad request — invalid `source_type` or missing required fields
        '401':
          description: Unauthorized — invalid or missing API key
        '404':
          description: Source batch not found or not accessible to the caller
        '422':
          description: Source batch already contains the maximum number of sources (10)
components:
  schemas:
    UploadUrlRequest:
      type: object
      properties:
        name:
          type: string
          description: >-
            Display name for the source, typically the original filename. Used
            to construct the S3 object key.
          example: Q4_Revenue_Report.pdf
        source_type:
          type: string
          description: >-
            File extension. Determines the expected `Content-Type` baked into
            the presigned URL signature.
          enum:
            - pdf
            - docx
            - xlsx
            - pptx
            - doc
            - txt
          example: pdf
        source_batch_id:
          type: string
          format: uuid
          description: >-
            Source batch the upload will be registered under. Must be created
            first via `POST /v1/source_batches`. Per-batch source limit (10) is
            enforced here.
          example: 123e4567-e89b-12d3-a456-426614174000
      required:
        - name
        - source_type
        - source_batch_id
    UploadUrlResponse:
      type: object
      properties:
        upload_url:
          type: string
          format: uri
          description: >-
            Presigned S3 PUT URL. Single-use, signed with the `content_type`
            value below. Send the file body via `PUT <upload_url>` with header
            `Content-Type: <content_type>`.
          example: >-
            https://linq-vertex-documents.s3.amazonaws.com/uploads/1715000000000_a1b2c3d4_Q4_Revenue_Report.pdf/original.pdf?X-Amz-Algorithm=...&X-Amz-Signature=...
        file_key:
          type: string
          description: >-
            Server-generated object key. Pass this as `file_key` in the
            subsequent `POST /v1/sources` call to register the uploaded file.
          example: uploads/1715000000000_a1b2c3d4_Q4_Revenue_Report.pdf/original.pdf
        content_type:
          type: string
          description: >-
            Content-Type baked into the presigned URL signature. Client MUST
            send this exact value as the `Content-Type` header on the PUT,
            otherwise S3 rejects with HTTP 403 (`SignatureDoesNotMatch`).
          example: application/pdf
        expires_in:
          type: integer
          description: Seconds until `upload_url` expires. After this, request a new URL.
          example: 600
      required:
        - upload_url
        - file_key
        - content_type
        - expires_in
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-KEY

````