> ## Documentation Index
> Fetch the complete documentation index at: https://cortex-ad5578da.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Upload Document

> Upload one or more documents for ingestion.
Supports both single and batch uploads.
For single file: send one file with the 'files' field.
For batch: send multiple files with the 'files' field (FastAPI will parse as list).

export const TableOfContents = ({title = 'On this page', items, minHeadingLevel = 2, maxHeadingLevel = 3, className = '', activeClassName = 'text-primary dark:text-primary-light border-primary dark:border-primary-light hover:border-primary dark:hover:border-primary-light', inactiveClassName = 'hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300'}) => {
  const [toc, setToc] = useState(items ?? []);
  const [activeId, setActiveId] = useState('');
  useEffect(() => {
    if (items && items.length) return;
    if (typeof document === 'undefined') return;
    const selectors = [];
    for (let lvl = minHeadingLevel; lvl <= maxHeadingLevel; lvl++) {
      selectors.push(`h${lvl}`);
    }
    const nodes = Array.from(document.querySelectorAll(selectors.join(','))).filter(el => el.id);
    const built = [];
    let currentTop = null;
    nodes.forEach(el => {
      const level = Number(el.tagName.slice(1));
      const node = {
        id: el.id,
        label: el.textContent.trim(),
        href: `#${el.id}`,
        children: []
      };
      if (level === minHeadingLevel) {
        built.push(node);
        currentTop = node;
      } else if (level > minHeadingLevel && currentTop) {
        currentTop.children.push(node);
      } else {
        built.push(node);
      }
    });
    setToc(built);
  }, [items, minHeadingLevel, maxHeadingLevel]);
  useEffect(() => {
    if (typeof document === 'undefined') return;
    const applyHash = () => {
      const h = window.location.hash.replace('#', '');
      if (h) setActiveId(h);
    };
    applyHash();
    const observer = new IntersectionObserver(entries => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          setActiveId(entry.target.id);
        }
      });
    }, {
      rootMargin: '0px 0px -70% 0px',
      threshold: 0.1
    });
    const ids = toc.flatMap(i => [i, ...i.children ?? []]).map(i => i.id);
    ids.forEach(id => {
      const el = document.getElementById(id);
      if (el) observer.observe(el);
    });
    window.addEventListener('hashchange', applyHash);
    return () => {
      window.removeEventListener('hashchange', applyHash);
      observer.disconnect();
    };
  }, [toc]);
  const Item = ({node, depth = 0}) => {
    const isActive = activeId === node.id;
    return <li className="toc-item relative ml-6" data-depth={depth}>
        <a href={node.href} className={`py-1 block font-medium ${isActive ? activeClassName : inactiveClassName}`} style={depth > 0 ? {
      marginLeft: `${depth}rem`
    } : {}} onClick={() => setActiveId(node.id)}>
          {node.label}
        </a>
        {node.children && node.children.length > 0 && <>
            {node.children.map(child => <Item node={child} depth={depth + 1} key={child.href} />)}
          </>}
      </li>;
  };
  const data = toc && toc.length ? toc : items || [];
  return <div className={`text-gray-600 text-sm leading-6 w-[18rem] pb-4 -mt-10 pt-10 hidden xl:block ${className}`} id="table-of-contents-custom">
      <ul id="table-of-contents-custom-content" className="toc">
        <li className="toc-item relative">
          <div className="text-gray-700 dark:text-gray-300 font-medium flex items-center space-x-2 py-1">
            <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2" xmlns="http://www.w3.org/2000/svg" className="h-3 w-3">
              <path d="M2.44434 12.6665H13.5554" strokeLinecap="round" strokeLinejoin="round"></path>
              <path d="M2.44434 3.3335H13.5554" strokeLinecap="round" strokeLinejoin="round"></path>
              <path d="M2.44434 8H7.33323" strokeLinecap="round" strokeLinejoin="round"></path>
            </svg>
            <span>{title}</span>
          </div>
        </li>
        {data.map(node => <Item node={node} key={node.href} />)}
      </ul>
    </div>;
};

<Panel>
  <TableOfContents />
</Panel>

<Tip> Hit the `Try it` button to try this API now in our playground. It's the best way to check the full request and response in one place, customize your parameters, and generate ready-to-use code snippets.</Tip>

### Examples

<Tabs>
  <Tab title="API Request">
    ```bash theme={null}
    curl -X POST https://api.usecortex.ai/upload-document \
    -F "tenant_id=tenant_123" \
    -F "upsert=true" \
    -F "files=@a.pdf" \
    -F "files=@b.pdf" \
    -F 'file_metadata=[
    {
      "file_id": "doc_a",
      "tenant_metadata": { "dept": "sales" },
      "document_metadata": { "author": "Alice" },
      "relations": false
    },
    {
      "file_id": "doc_b",
      "tenant_metadata": { "dept": "marketing" },
      "document_metadata": { "author": "Bob" },
      "relations": true
    }
    ]'
    ```
  </Tab>

  <Tab title="TypeScript">
    ```ts theme={null}
    import fs from 'fs';

    const uploadResult = await client.upload.uploadDocument({
      file: fs.readFileSync("example-file.pdf"),
      tenant_id: "tenant_1234",
      sub_tenant_id: "sub_tenant_4567",
      file_id: "doc_123456",
      tenant_metadata: {},
      document_metadata: {}
    });
    ```
  </Tab>

  <Tab title="Python (Sync)">
    ```python theme={null}
    # Async usage is similar, just use async_client and await
    with open("example-file.pdf", 'rb') as file_obj:
        file_data = ("example-file.pdf", file_obj)
        upload_result = client.upload.upload_document(
            tenant_id="tenant_1234",
            sub_tenant_id="sub_tenant_4567",
            file=file_data,
            file_id="doc_123456",
            tenant_metadata={},
            document_metadata={}
        )
    ```
  </Tab>
</Tabs>

Upload documents to your tenant's knowledge base for processing, chunking, and indexing to enable search and retrieval.

## Metadata Parameters

When uploading multiple files, you can provide metadata for each file using the `file_metadata` parameter. This allows you to associate custom metadata, organize documents, and control processing behavior on a per-file basis.

### `file_metadata` Array

The `file_metadata` parameter accepts a JSON array where each object corresponds to one of the uploaded files. The order of metadata objects should match the order of files in the `files` parameter.

**Structure:**

```json theme={null}
[
  {
    "file_id": "string",
    "tenant_metadata": {},
    "document_metadata": {},
    "relations": boolean
  }
]
```

### Metadata Fields

#### `file_id` (string, optional)

* **Description**: A unique identifier for the document. If not provided, the system will auto-generate one.
* **Use Case**: Use this to reference the document later, enable idempotent uploads, or maintain your own document naming scheme.
* **Example**: `"doc_a"`, `"invoice_2024_001"`, `"manual_v2.3"`

#### `tenant_metadata` (object, optional)

* **Description**: Key-value pairs that represent tenant-level metadata. This metadata is shared across all documents within the tenant and is useful for organization-wide filtering and categorization.
* **Use Case**: Store department information, project tags, organizational units, or any tenant-scoped attributes that help organize and filter documents.
* **Example**:
  ```json theme={null}
  {
    "dept": "sales",
    "project": "Q4_2024",
    "region": "us-west"
  }
  ```
* **Note**: This metadata is indexed and can be used for filtering in search queries.

#### `document_metadata` (object, optional)

* **Description**: Key-value pairs that represent document-specific metadata. This metadata is unique to each document and provides context about the document itself.
* **Use Case**: Store document-specific information like author, creation date, document type, version, or any attributes that describe the individual document.
* **Example**:
  ```json theme={null}
  {
    "author": "Alice",
    "created_date": "2024-01-15",
    "document_type": "invoice",
    "version": "1.0"
  }
  ```
* **Note**: This metadata is indexed and can be used for filtering in search queries.

#### `relations` (boolean, optional)

* **Description**: Controls whether the system should extract and index relationships between entities in the document. When set to `true`, the system will analyze the document for entity relationships and create a knowledge graph.
* **Use Case**: Enable relationship extraction for documents where understanding connections between entities (people, places, concepts) is important for your use case.
* **Default**: `false`
* **Example**:
  * `true`: Extract relationships for documents like organizational charts, knowledge bases, or interconnected documentation
  * `false`: Skip relationship extraction for simple documents or when graph features aren't needed

<Info>
  **Metadata Ordering**: The order of objects in the `file_metadata` array should match the order of files in the `files` parameter. The first metadata object applies to the first file, the second to the second file, and so on.
</Info>

<Note>
  **Metadata Indexing**: Both `tenant_metadata` and `document_metadata` are indexed and can be used to filter search results. This enables powerful query capabilities like "find all sales documents from Q4" or "retrieve documents authored by Alice".
</Note>

## Supported file formats

<Info>
  **Complete Reference**: For a comprehensive list of all supported file formats with detailed information, see our [Supported File Formats](/essentials/file-formats) documentation.
</Info>

<Warning>
  **Unsupported File Formats**: If you attempt to upload a file format that is not supported, you will receive an error response with status code `400` and the message: `"Unsupported file format: [filename]. Please check our supported file formats documentation."` Ensure your files are in one of the supported formats listed above before uploading.
</Warning>

## Document Processing Pipeline

When you upload a document, it goes through a comprehensive processing pipeline designed to make your content searchable and retrievable:

### 1. **Immediate Upload & Queue**

* Your document is immediately accepted and stored securely
* It's added to our processing queue for background processing
* You receive a confirmation response with a `source_id` for tracking

### 2. **Processing Phase**

Our system automatically handles:

* **Content Extraction**: Extracting text from various formats (PDF, DOCX, TXT, etc.)
* **Document Parsing**: Understanding document structure, headers, and formatting
* **Text Cleaning**: Removing formatting artifacts and normalizing content

### 3. **Intelligent Chunking**

* Documents are split into semantically meaningful chunks
* Chunk size is optimized for both context preservation and search accuracy
* Overlapping boundaries ensure no information is lost between chunks
* Metadata is preserved and associated with each chunk

### 4. **Embedding Generation**

* Each chunk is converted into high-dimensional vector embeddings
* Embeddings capture semantic meaning and context
* Vectors are optimized for similarity search and retrieval

### 5. **Indexing & Database Updates**

* Embeddings are stored in our vector database for fast similarity search
* Full-text search indexes are created for keyword-based queries
* Metadata is indexed for filtering and faceted search
* Cross-references are established for related documents

### 6. **Quality Assurance**

* Automated quality checks ensure processing accuracy
* Content validation verifies extracted text completeness
* Embedding quality is assessed for optimal retrieval performance

<Note>
  **Processing Time**: Most documents are fully processed and searchable within 1-5 minutes. Larger documents (100+ pages) may take up to 15 minutes. You can check processing status using the document ID returned in the response.
</Note>

<Note>
  **Default Sub-Tenant Behavior**: If you don't specify a `sub_tenant_id`, the document will be uploaded to the default sub-tenant created when your tenant was set up. This is perfect for organization-wide documents that should be accessible across all departments.
</Note>

> **File ID Management**: The system uses a priority-based approach for file ID assignment:
>
> 1. **First Priority**: If you provide a `file_id` in the `file_metadata`
> 2. **Auto-Generation**: If neither source provides a `file_id`, the system will automatically generate a unique identifier

### **Duplicate File ID Behavior**

When you upload a document with a `file_id` that already exists in your tenant:

* **Overwrite Behavior**: The existing document with the same `file_id` will be **completely replaced** with the new document
* **Processing**: The new document will go through the full processing pipeline (content extraction, chunking, embedding generation, indexing)
* **Search Results**: Previous search results and embeddings from the old document will be replaced with the new document's content
* **Idempotency**: Uploading the same document with the same `file_id` multiple times is safe and will result in the same final state

<Warning>
  **Important**: When overwriting an existing document, all previous chunks, embeddings, and search indexes associated with that `file_id` will be permanently removed and replaced. This action cannot be undone.
</Warning>

## Processing Status & Monitoring

After uploading, you can monitor your document's processing status:

### **Immediate Response**

Upon successful upload, you'll receive:

```json theme={null}
{
  "filename": "file_abc.pdf",
  "source_id": "doc_123456",
  "status": "queued"
}
```

### **Processing States**

Your document will progress through these states:

* **`queued`**: Document is in the processing queue, waiting to be processed
* **`in_progress`**: Document is actively being processed (includes content extraction, chunking, embedding generation, and indexing)
* **`success`**: Document is fully processed and searchable
* **`errored`**: Processing encountered an error (rare occurrence)

<Info>
  **In-Progress Details**: While the status shows `in_progress`, the system is actually performing multiple steps: content extraction, document parsing, intelligent chunking, embedding generation, and database indexing. These happen sequentially but are all part of the single `in_progress` state.
</Info>

### **When Your Document is Ready**

Once processing is complete, your document will be:

* ✅ **Searchable** via semantic search and Q\&A endpoints
* ✅ **Available** for AI-powered applications
* ✅ **Indexed** for fast query performance

<Warning>
  **Important**: Don't attempt to search or retrieve your document immediately after upload. Wait for processing to complete (typically 1-5 minutes) to ensure optimal results.
</Warning>

## Best Practices

### **Document Preparation**

* **File Size**: Documents up to 50MB are processed efficiently
* **Content Quality**: Clear, well-structured documents produce better embeddings
* **Metadata**: Include rich metadata for better filtering and organization

### **Processing Optimization**

* **Batch Uploads**: For multiple documents, consider using our batch upload endpoint
* **Metadata Consistency**: Use consistent metadata schemas across your organization
* **File Naming**: Descriptive filenames help with document identification

### **Troubleshooting**

**Document Not Appearing in Search?**

* Wait 5-10 minutes for processing to complete
* Check if the document status is `errored` (rare occurrence)
* Verify your search query and filters

**Slow Processing?**

* Large documents (100+ pages) take longer to process
* Complex formatting may require additional processing time
* High system load may temporarily slow processing

**Processing Failures?**

* If status shows `errored`, ensure your document isn't corrupted or password-protected
* Check that the file format is supported (see Supported File Formats section above)
* Verify your API key has sufficient permissions
* For unsupported formats, you'll receive a `400` error with the message: `"Unsupported file format: [filename]. Please check our supported file formats documentation."`

<Info>
  **Need Help?** If a document fails to process or you're experiencing issues, contact our support team with the `file_id` for assistance.
</Info>

## Error Responses

All endpoints return consistent error responses following the standard format. For detailed error information, see our [Error Responses](/api-reference/error-responses) documentation.


## OpenAPI

````yaml POST /ingestion/upload-document
openapi: 3.1.0
info:
  title: Cortex SDK API
  description: REST APIs for Cortex AI retrieval engine
  version: 0.0.1
servers:
  - url: /
    description: Local
    x-fern-server-name: cortex-backend-local
  - url: https://api.usecortex.ai
    description: Production
    x-fern-server-name: cortex-prod
    x-fern-audiences:
      - public
  - url: https://preprod.usecortex.ai
    description: Staging
    x-fern-server-name: cortex-staging
security: []
paths:
  /ingestion/upload-document:
    post:
      tags:
        - ingestion
      summary: Upload Document
      description: >-
        Upload one or more documents for ingestion.

        Supports both single and batch uploads.

        For single file: send one file with the 'files' field.

        For batch: send multiple files with the 'files' field (FastAPI will
        parse as list).
      operationId: upload_document_ingestion_upload_document_post
      requestBody:
        content:
          multipart/form-data:
            schema:
              $ref: >-
                #/components/schemas/Body_upload_document_ingestion_upload_document_post
        required: true
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SourceUploadResponse'
        '400':
          description: Bad Request - Invalid input parameters
          content:
            application/json:
              schema:
                $ref: >-
                  #/components/schemas/cortex__models__response__commons__ActualErrorResponse
        '401':
          description: Unauthorized - Authentication required
          content:
            application/json:
              schema:
                $ref: >-
                  #/components/schemas/cortex__models__response__commons__ActualErrorResponse
        '403':
          description: Forbidden - Access denied
          content:
            application/json:
              schema:
                $ref: >-
                  #/components/schemas/cortex__models__response__commons__ActualErrorResponse
        '404':
          description: Not Found - Resource does not exist
          content:
            application/json:
              schema:
                $ref: >-
                  #/components/schemas/cortex__models__response__commons__ActualErrorResponse
        '422':
          description: Unprocessable Entity - Validation failed
          content:
            application/json:
              schema:
                $ref: >-
                  #/components/schemas/cortex__models__response__commons__ActualErrorResponse
        '500':
          description: Internal Server Error
          content:
            application/json:
              schema:
                $ref: >-
                  #/components/schemas/cortex__models__response__commons__ActualErrorResponse
        '503':
          description: Service Unavailable
          content:
            application/json:
              schema:
                $ref: >-
                  #/components/schemas/cortex__models__response__commons__ActualErrorResponse
      security:
        - HTTPBearer: []
components:
  schemas:
    Body_upload_document_ingestion_upload_document_post:
      properties:
        files:
          items:
            type: string
            format: binary
          type: array
          title: Files
          description: Files to be uploaded
        tenant_id:
          type: string
          title: Tenant Id
          description: Unique identifier for the tenant/organization
          example: tenant_1234
        sub_tenant_id:
          type: string
          title: Sub Tenant Id
          description: >-
            Optional sub-tenant identifier used to organize data within a
            tenant. If omitted, the default sub-tenant created during tenant
            setup will be used.
          default: ''
          example: sub_tenant_4567
        upsert:
          type: boolean
          title: Upsert
          default: true
          example: true
        file_metadata:
          anyOf:
            - type: string
            - type: 'null'
          title: File Metadata
          description: JSON Array of file metadata objects
      type: object
      required:
        - files
        - tenant_id
      title: Body_upload_document_ingestion_upload_document_post
    SourceUploadResponse:
      properties:
        success:
          type: boolean
          title: Success
          default: true
          example: true
        message:
          type: string
          title: Message
          default: Upload initiated successfully
        results:
          items:
            $ref: '#/components/schemas/SourceUploadResultItem'
          type: array
          title: Results
          description: List of upload results for each source.
          example: []
        success_count:
          type: integer
          title: Success Count
          description: Number of sources successfully queued.
          default: 0
          example: 1
        failed_count:
          type: integer
          title: Failed Count
          description: Number of sources that failed to upload.
          default: 0
          example: 1
      type: object
      title: SourceUploadResponse
    cortex__models__response__commons__ActualErrorResponse:
      properties:
        detail:
          $ref: >-
            #/components/schemas/cortex__models__response__commons__ErrorResponse
      type: object
      required:
        - detail
      title: ActualErrorResponse
    SourceUploadResultItem:
      properties:
        source_id:
          type: string
          title: Source Id
          description: Unique identifier for the uploaded source.
          example: CortexDoc1234
        filename:
          anyOf:
            - type: string
            - type: 'null'
          title: Filename
          description: Original filename if present.
        status:
          $ref: '#/components/schemas/SourceStatus'
          description: Initial processing status.
          default: queued
        error:
          anyOf:
            - type: string
            - type: 'null'
          title: Error
          description: Error message if upload failed.
      type: object
      required:
        - source_id
      title: SourceUploadResultItem
    cortex__models__response__commons__ErrorResponse:
      properties:
        success:
          type: boolean
          title: Success
          default: false
          example: true
        message:
          type: string
          title: Message
          default: Error occurred
        error_code:
          anyOf:
            - type: string
            - type: 'null'
          title: Error Code
      type: object
      title: ErrorResponse
    SourceStatus:
      type: string
      enum:
        - queued
        - processing
        - completed
        - failed
      title: SourceStatus
  securitySchemes:
    HTTPBearer:
      type: http
      scheme: bearer

````