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

# Uploading Documents

> Upload and process documents for semantic search in your knowledge base

## Overview

Once you've created knowledge folders, you can upload documents to make them searchable. This guide covers the upload process, troubleshooting, and best practices for successful document processing.

## Upload Process

Anam uses a secure three-step presigned URL process for all document uploads.

<Info>
  Document uploads are subject to file size limits. **Need higher limits?**
  Contact us about Enterprise plans with custom limits.
</Info>

## How It Works

For security and performance, all document uploads use a presigned URL flow:

<Steps>
  <Step title="Request presigned URL">
    Request an upload URL from Anam:

    <CodeGroup>
      ```bash cURL theme={"system"}
      curl -X POST 'https://api.anam.ai/v1/knowledge/groups/FOLDER_ID/documents/presigned-upload' \
        -H 'Authorization: Bearer YOUR_API_KEY' \
        -H 'Content-Type: application/json' \
        -d '{
          "filename": "large-document.pdf",
          "contentType": "application/pdf",
          "fileSize": 10485760
        }'
      ```

      ```javascript JavaScript theme={"system"}
      const response = await fetch(
        `https://api.anam.ai/v1/knowledge/groups/${folderId}/documents/presigned-upload`,
        {
          method: "POST",
          headers: {
            Authorization: `Bearer ${apiKey}`,
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            filename: "large-document.pdf",
            contentType: "application/pdf",
            fileSize: file.size,
          }),
        }
      );

      const { uploadUrl, documentId } = await response.json();
      ```

      ```python Python theme={"system"}
      response = requests.post(
          f'https://api.anam.ai/v1/knowledge/groups/{folder_id}/documents/presigned-upload',
          headers={
              'Authorization': f'Bearer {api_key}',
              'Content-Type': 'application/json'
          },
          json={
              'filename': 'large-document.pdf',
              'contentType': 'application/pdf',
              'fileSize': file_size
          }
      )

      data = response.json()
      upload_url = data['uploadUrl']
      document_id = data['documentId']
      ```
    </CodeGroup>

    **Response**:

    ```json theme={"system"}
    {
      "uploadUrl": "https://storage.cloudflare.com/presigned-url-here",
      "documentId": "doc-uuid-123"
    }
    ```
  </Step>

  <Step title="Upload to presigned URL">
    Upload the file directly to cloud storage:

    <CodeGroup>
      ```bash cURL theme={"system"}
      curl -X PUT 'PRESIGNED_URL_FROM_STEP_1' \
        -H 'Content-Type: application/pdf' \
        --data-binary '@large-document.pdf'
      ```

      ```javascript JavaScript theme={"system"}
      const uploadResponse = await fetch(uploadUrl, {
        method: "PUT",
        headers: {
          "Content-Type": "application/pdf",
        },
        body: file,
      });

      if (!uploadResponse.ok) {
        throw new Error("File upload to storage failed");
      }
      ```

      ```python Python theme={"system"}
      with open('large-document.pdf', 'rb') as f:
          upload_response = requests.put(
              upload_url,
              headers={'Content-Type': 'application/pdf'},
              data=f
          )

      if upload_response.status_code != 200:
          raise Exception('File upload to storage failed')
      ```
    </CodeGroup>

    <Info>
      This step uploads directly to cloud storage, bypassing Anam's API servers for better performance with large files.
    </Info>
  </Step>

  <Step title="Confirm upload">
    Notify Anam that the upload is complete:

    <CodeGroup>
      ```bash cURL theme={"system"}
      curl -X POST 'https://api.anam.ai/v1/knowledge/documents/DOCUMENT_ID/confirm-upload' \
        -H 'Authorization: Bearer YOUR_API_KEY' \
        -H 'Content-Type: application/json' \
        -d '{
          "fileSize": 10485760
        }'
      ```

      ```javascript JavaScript theme={"system"}
      const confirmResponse = await fetch(
        `https://api.anam.ai/v1/knowledge/documents/${documentId}/confirm-upload`,
        {
          method: "POST",
          headers: {
            Authorization: `Bearer ${apiKey}`,
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            fileSize: file.size,
          }),
        }
      );

      const document = await confirmResponse.json();
      console.log("Upload confirmed, processing started");
      ```

      ```python Python theme={"system"}
      confirm_response = requests.post(
          f'https://api.anam.ai/v1/knowledge/documents/{document_id}/confirm-upload',
          headers={
              'Authorization': f'Bearer {api_key}',
              'Content-Type': 'application/json'
          },
          json={'fileSize': file_size}
      )

      document = confirm_response.json()
      print('Upload confirmed, processing started')
      ```
    </CodeGroup>

    <Check>
      Processing begins automatically. The document will be ready for search in \~30 seconds.
    </Check>
  </Step>
</Steps>

### Complete Presigned Upload Example

<CodeGroup>
  ```javascript JavaScript theme={"system"}
  async function uploadLargeDocument(file, folderId, apiKey) {
    try {
      // Step 1: Get presigned URL
      const presignedResponse = await fetch(
        `https://api.anam.ai/v1/knowledge/groups/${folderId}/documents/presigned-upload`,
        {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${apiKey}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            filename: file.name,
            contentType: file.type,
            fileSize: file.size
          })
        }
      );

      if (!presignedResponse.ok) {
        throw new Error('Failed to get presigned URL');
      }

      const { uploadUrl, documentId } = await presignedResponse.json();
      console.log('Got presigned URL for document:', documentId);

      // Step 2: Upload to storage
      const uploadResponse = await fetch(uploadUrl, {
        method: 'PUT',
        headers: {
          'Content-Type': file.type
        },
        body: file
      });

      if (!uploadResponse.ok) {
        throw new Error('Failed to upload file to storage');
      }

      console.log('File uploaded to storage');

      // Step 3: Confirm upload
      const confirmResponse = await fetch(
        `https://api.anam.ai/v1/knowledge/documents/${documentId}/confirm-upload`,
        {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${apiKey}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            fileSize: file.size
          })
        }
      );

      if (!confirmResponse.ok) {
        throw new Error('Failed to confirm upload');
      }

      const document = await confirmResponse.json();
      console.log('Upload complete! Document ID:', document.id);
      console.log('Status:', document.status);

      return document;

    } catch (error) {
      console.error('Upload error:', error);
      throw error;
    }
  }

  // Usage with file size check
  async function uploadDocument(file, folderId, apiKey) {
  console.log('Using presigned URL upload');
  return await uploadLargeDocument(file, folderId, apiKey);
  }

  ```

  ```python Python theme={"system"}
  import requests
  import os

  def upload_document(file_path, folder_id, api_key):
      """Upload a document using presigned URL"""

      filename = os.path.basename(file_path)
      file_size = os.path.getsize(file_path)
      content_type = 'application/pdf'  # Adjust based on file type

      try:
          # Step 1: Get presigned URL
          presigned_response = requests.post(
              f'https://api.anam.ai/v1/knowledge/groups/{folder_id}/documents/presigned-upload',
              headers={
                  'Authorization': f'Bearer {api_key}',
                  'Content-Type': 'application/json'
              },
              json={
                  'filename': filename,
                  'contentType': content_type,
                  'fileSize': file_size
              }
          )
          presigned_response.raise_for_status()

          data = presigned_response.json()
          upload_url = data['uploadUrl']
          document_id = data['documentId']

          print(f'Got presigned URL for document: {document_id}')

          # Step 2: Upload to storage
          with open(file_path, 'rb') as f:
              upload_response = requests.put(
                  upload_url,
                  headers={'Content-Type': content_type},
                  data=f
              )
          upload_response.raise_for_status()

          print('File uploaded to storage')

          # Step 3: Confirm upload
          confirm_response = requests.post(
              f'https://api.anam.ai/v1/knowledge/documents/{document_id}/confirm-upload',
              headers={
                  'Authorization': f'Bearer {api_key}',
                  'Content-Type': 'application/json'
              },
              json={'fileSize': file_size}
          )
          confirm_response.raise_for_status()

          document = confirm_response.json()
          print(f'Upload complete! Document ID: {document["id"]}')
          print(f'Status: {document["status"]}')

          return document

      except requests.exceptions.RequestException as e:
          print(f'Upload error: {e}')
          raise

  # Usage with file size check
  def upload_document(file_path, folder_id, api_key):
      """Upload document using the presigned URL method"""

      print('Using presigned URL upload')
      return upload_large_document(file_path, folder_id, api_key)
  ```
</CodeGroup>

## Monitoring Upload Progress

### Check Document Status

After uploading, monitor the document's processing status:

<CodeGroup>
  ```bash cURL theme={"system"}
  curl -X GET 'https://api.anam.ai/v1/knowledge/documents/DOCUMENT_ID' \
    -H 'Authorization: Bearer YOUR_API_KEY'
  ```

  ```javascript JavaScript theme={"system"}
  async function checkDocumentStatus(documentId, apiKey) {
    const response = await fetch(
      `https://api.anam.ai/v1/knowledge/documents/${documentId}`,
      {
        headers: {
          Authorization: `Bearer ${apiKey}`,
        },
      }
    );

    const document = await response.json();
    return document.status;
  }

  // Poll until READY
  async function waitForProcessing(documentId, apiKey) {
    const maxAttempts = 60; // 60 attempts = 5 minutes max
    let attempts = 0;

    while (attempts < maxAttempts) {
      const status = await checkDocumentStatus(documentId, apiKey);
      console.log(`Status: ${status}`);

      if (status === "READY") {
        console.log("Document is ready for search!");
        return true;
      }

      if (status === "FAILED") {
        throw new Error("Document processing failed");
      }

      // Wait 5 seconds before next check
      await new Promise((resolve) => setTimeout(resolve, 5000));
      attempts++;
    }

    throw new Error("Processing timeout");
  }
  ```

  ```python Python theme={"system"}
  import time

  def check_document_status(document_id, api_key):
      """Check the processing status of a document"""

      response = requests.get(
          f'https://api.anam.ai/v1/knowledge/documents/{document_id}',
          headers={'Authorization': f'Bearer {api_key}'}
      )
      response.raise_for_status()

      document = response.json()
      return document['status']

  def wait_for_processing(document_id, api_key, max_attempts=60):
      """Poll until document is ready or fails"""

      attempts = 0

      while attempts < max_attempts:
          status = check_document_status(document_id, api_key)
          print(f'Status: {status}')

          if status == 'READY':
              print('Document is ready for search!')
              return True

          if status == 'FAILED':
              raise Exception('Document processing failed')

          # Wait 5 seconds before next check
          time.sleep(5)
          attempts += 1

      raise Exception('Processing timeout')
  ```
</CodeGroup>

**Document statuses**:

* `UPLOADED`: File uploaded, waiting for processing
* `PROCESSING`: Content is being extracted and indexed
* `READY`: Document is searchable
* `FAILED`: Processing failed (check error message)

## Batch Uploads

Upload multiple documents efficiently:

```javascript theme={"system"}
async function batchUpload(files, folderId, apiKey) {
  const maxDirectUploadSize = 4 * 1024 * 1024; // 4MB
  const results = [];

  // Process files concurrently (4 at a time)
  const batchSize = 4;
  for (let i = 0; i < files.length; i += batchSize) {
    const batch = files.slice(i, i + batchSize);

    const batchPromises = batch.map(async (file) => {
      try {
        let document;

        if (file.size <= maxDirectUploadSize) {
          document = await uploadSmallDocument(file, folderId, apiKey);
        } else {
          document = await uploadLargeDocument(file, folderId, apiKey);
        }

        console.log(`✓ Uploaded: ${file.name}`);
        return { file: file.name, success: true, documentId: document.id };
      } catch (error) {
        console.error(`✗ Failed: ${file.name}`, error.message);
        return { file: file.name, success: false, error: error.message };
      }
    });

    const batchResults = await Promise.all(batchPromises);
    results.push(...batchResults);
  }

  const successful = results.filter((r) => r.success).length;
  const failed = results.filter((r) => !r.success).length;

  console.log(`\nBatch upload complete:`);
  console.log(`  Successful: ${successful}`);
  console.log(`  Failed: ${failed}`);

  return results;
}
```

<Tip>
  Process documents in batches of 4 for optimal performance. The system handles
  up to 4 documents concurrently.
</Tip>

## Troubleshooting

<AccordionGroup>
  <Accordion title="File too large">
    **Error**: `File size exceeds limit`

    **Solutions**:

    * Split the document into smaller files
    * Compress images in PDFs
    * Remove unnecessary pages or content
    * Contact us about Enterprise plans with higher file size limits
  </Accordion>

  <Accordion title="Quota exceeded">
    **Error**: `Upload quota exceeded`

    **Solutions**:

    * Delete unused or outdated documents to free up quota
    * Contact us about Enterprise plans with higher storage limits
    * Check current usage: `GET /v1/knowledge/usage`
  </Accordion>

  <Accordion title="Upload fails immediately">
    **Possible causes**:

    * Invalid API key
    * Invalid folder ID
    * Unsupported file type

    **Solutions**:

    1. Verify API key is valid and has proper permissions
    2. Confirm folder ID exists: `GET /v1/knowledge/groups`
    3. Check file extension is supported (PDF, TXT, MD, DOCX, CSV, JSON, LOG)
    4. Review error message in response
  </Accordion>

  <Accordion title="Processing stuck">
    **Status remains PROCESSING for > 5 minutes**

    **Possible causes**:

    * Very large file (40-50MB)
    * Complex PDF with many images
    * Service temporarily slow

    **Solutions**:

    1. Wait up to 10 minutes for large files
    2. Check document status via API
    3. If stuck for >10 minutes, delete and re-upload
    4. Contact support if issue persists
  </Accordion>

  <Accordion title="Processing failed">
    **Status changes to FAILED**

    **Common causes**:

    * Corrupted file
    * Encrypted or password-protected PDF
    * Invalid file format despite correct extension
    * File contains only images (no text)

    **Solutions**:

    1. Check error message in document details
    2. Verify file opens correctly on your computer
    3. Remove password protection from PDFs
    4. Ensure PDFs contain extractable text (not just images)
    5. Try converting to a different supported format
  </Accordion>

  <Accordion title="Presigned URL expires">
    **Error when uploading to presigned URL**

    **Solutions**:

    * Presigned URLs expire after 1 hour
    * Request a new presigned URL if expired
    * Upload the file immediately after receiving the URL
  </Accordion>
</AccordionGroup>

## Best Practices

<AccordionGroup>
  <Accordion title="Always use presigned URL upload">
    ```javascript theme={"system"}
    // All uploads use the presigned URL flow for security and consistency
    await presignedUpload(file);
    ```
  </Accordion>

  <Accordion title="Handle errors gracefully">
    ```javascript theme={"system"}
    try {
      const document = await uploadDocument(file, folderId, apiKey);

      // Wait for processing
      await waitForProcessing(document.id, apiKey);

      console.log('Document ready!');

    } catch (error) {
      if (error.message.includes('quota exceeded')) {
        // Show quota upgrade prompt
        showQuotaUpgradeDialog();
      } else if (error.message.includes('file too large')) {
        // Suggest file splitting
        showFileTooLargeError();
      } else {
        // Generic error handling
        showErrorNotification(error.message);
      }
    }
    ```
  </Accordion>

  <Accordion title="Show upload progress">
    ```javascript theme={"system"}
    async function uploadWithProgress(file, folderId, apiKey, onProgress) {
      // For presigned URL uploads, track progress
      const xhr = new XMLHttpRequest();

      return new Promise((resolve, reject) => {
        xhr.upload.addEventListener('progress', (e) => {
          if (e.lengthComputable) {
            const percentComplete = (e.loaded / e.total) * 100;
            onProgress(percentComplete);
          }
        });

        xhr.addEventListener('load', () => {
          if (xhr.status === 200) {
            resolve();
          } else {
            reject(new Error('Upload failed'));
          }
        });

        xhr.open('PUT', uploadUrl);
        xhr.setRequestHeader('Content-Type', file.type);
        xhr.send(file);
      });
    }
    ```
  </Accordion>

  <Accordion title="Validate before uploading">
    ```javascript theme={"system"}
    function validateFile(file) {
      const maxSize = 50 * 1024 * 1024; // 50MB
      const supportedTypes = [
        'application/pdf',
        'text/plain',
        'text/markdown',
        'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
        'text/csv',
        'application/json'
      ];

      if (file.size > maxSize) {
        throw new Error('File exceeds 50MB limit');
      }

      if (!supportedTypes.includes(file.type)) {
        throw new Error('Unsupported file type');
      }

      return true;
    }
    ```
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Create Knowledge Tools" icon="wrench" href="/personas/knowledge/tools">
    Enable semantic search with knowledge tools
  </Card>

  <Card title="Knowledge Base Setup" icon="folder" href="/personas/knowledge/setup">
    Organize documents with folders
  </Card>

  <Card title="API Reference" icon="code" href="/api-reference/knowledge/upload-knowledge-group-document">
    Complete API documentation
  </Card>

  <Card title="Concepts" icon="book" href="/personas/knowledge/overview">
    Learn how RAG works
  </Card>
</CardGroup>
