HyperSaaS
FrontendDocuments

Upload Flow

Client-side S3 presigned URL upload with progress tracking.

Document upload uses a three-step presigned URL flow. The file goes directly from the browser to S3, bypassing the Next.js and Django servers.

Upload Component

DocumentUpload provides a drag-and-drop zone with multi-file support:

interface UploadFileState {
  file: File;
  status: "pending" | "uploading" | "confirming" | "done" | "error";
  progress: number;
  documentId?: string;
  error?: string;
}

Upload Steps

Step 1: Request Presigned URL

const res = await fetch(
  `/api/workspaces/${workspaceId}/documents/upload/`,
  {
    method: "POST",
    body: JSON.stringify({
      filename: file.name,
      content_type: file.type,
      file_size: file.size,
      knowledge_base_ids: knowledgeBaseId ? [knowledgeBaseId] : [],
    }),
  }
);

const { document_id, upload_url, s3_key } = await res.json();

The backend validates the file (type, size) and returns a presigned S3 PUT URL.

Step 2: Upload to S3

// Direct browser → S3 upload
const uploadRes = await fetch(upload_url, {
  method: "PUT",
  body: file,
  headers: { "Content-Type": file.type },
});

The file goes directly to S3 — no data passes through Next.js or Django.

Step 3: Confirm Upload

await fetch(
  `/api/workspaces/${workspaceId}/documents/${document_id}/confirm-upload/`,
  { method: "POST" }
);

This triggers the backend Celery task to parse, chunk, and embed the document.

Progress Tracking

Each file tracks its own status:

pending → uploading → confirming → done
                                └→ error

The UI shows:

  • Pending: File queued for upload
  • Uploading: Progress bar during S3 PUT
  • Confirming: Sending confirm request to backend
  • Done: Upload complete, ingestion started
  • Error: Failed at any step, with error message

Processing Status Polling

After upload confirmation, the document enters the ingestion pipeline. The UI polls the processing status:

const res = await fetch(
  `/api/workspaces/${workspaceId}/documents/${docId}/processing-status/`
);
const { status } = await res.json();
// status: "PENDING" | "STARTED" | "SUCCESS" | "FAILURE"

The DocumentStatusBadge component shows the current state:

StatusBadgeColor
pendingPendingOrange
processingProcessingOrange (animated)
completedReadyGreen
failedFailedRed

URL Ingestion

Documents can also be created from URLs (web pages or YouTube videos):

const res = await fetch(
  `/api/workspaces/${workspaceId}/documents/from-url/`,
  {
    method: "POST",
    body: JSON.stringify({
      url: "https://example.com/article",
      name: "Example Article",
      knowledge_base_ids: [kbId],
    }),
  }
);

The backend auto-detects the source type (web_url or youtube) and dispatches the appropriate Celery task.

File Type Support

ExtensionMIME Type
.pdfapplication/pdf
.docxapplication/vnd.openxmlformats-officedocument.wordprocessingml.document
.docapplication/msword
.txttext/plain
.csvtext/csv
.mdtext/markdown
.pptxapplication/vnd.openxmlformats-officedocument.presentationml.presentation
.xlsxapplication/vnd.openxmlformats-officedocument.spreadsheetml.sheet

Maximum file size: 50 MB (configurable on the backend).

On this page