Skip to main content

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.
Document uploads are subject to file size limits. Need higher limits? Contact us about Enterprise plans with custom limits.

How It Works

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

Request presigned URL

Request an upload URL from Anam:
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
  }'
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();
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']
Response:
{
  "uploadUrl": "https://storage.cloudflare.com/presigned-url-here",
  "documentId": "doc-uuid-123"
}
2

Upload to presigned URL

Upload the file directly to cloud storage:
curl -X PUT 'PRESIGNED_URL_FROM_STEP_1' \
  -H 'Content-Type: application/pdf' \
  --data-binary '@large-document.pdf'
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");
}
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')
This step uploads directly to cloud storage, bypassing Anam’s API servers for better performance with large files.
3

Confirm upload

Notify Anam that the upload is complete:
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
  }'
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");
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')
Processing begins automatically. The document will be ready for search in ~30 seconds.

Complete Presigned Upload Example

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);
}

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)

Monitoring Upload Progress

Check Document Status

After uploading, monitor the document’s processing status:
curl -X GET 'https://api.anam.ai/v1/knowledge/documents/DOCUMENT_ID' \
  -H 'Authorization: Bearer YOUR_API_KEY'
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");
}
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')
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:
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;
}
Process documents in batches of 4 for optimal performance. The system handles up to 4 documents concurrently.

Troubleshooting

Error: File size exceeds limitSolutions:
  • 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
Error: Upload quota exceededSolutions:
  • 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
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
Status remains PROCESSING for > 5 minutesPossible 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
Status changes to FAILEDCommon 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
Error when uploading to presigned URLSolutions:
  • Presigned URLs expire after 1 hour
  • Request a new presigned URL if expired
  • Upload the file immediately after receiving the URL

Best Practices

// All uploads use the presigned URL flow for security and consistency
await presignedUpload(file);
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);
  }
}
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);
  });
}
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;
}

Next Steps

Create Knowledge Tools

Enable semantic search with knowledge tools

Knowledge Base Setup

Organize documents with folders

API Reference

Complete API documentation

Concepts

Learn how RAG works
Last modified on April 29, 2026