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

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'
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