HyperSaaS
BackendDocuments & RAG

S3 Uploads

Presigned URL upload flow for document files.

HyperSaaS uses presigned S3 URLs so file uploads go directly from the client to S3, bypassing the Django server entirely.

Upload Flow

┌──────────┐         ┌──────────┐         ┌─────┐
│  Client   │         │  Django   │         │ S3  │
└────┬─────┘         └────┬─────┘         └──┬──┘
     │                     │                   │
     │  1. POST /upload/   │                   │
     │  {filename, size}   │                   │
     │────────────────────►│                   │
     │                     │                   │
     │  2. Presigned PUT   │                   │
     │  URL + Document ID  │                   │
     │◄────────────────────│                   │
     │                     │                   │
     │  3. PUT file ───────┼──────────────────►│
     │     (direct to S3)  │                   │
     │  ◄─────────────────────────── 200 OK ───│
     │                     │                   │
     │  4. POST /confirm/  │                   │
     │────────────────────►│                   │
     │                     │── Celery task ───►│
     │  5. {task_id}       │   (ingestion)     │
     │◄────────────────────│                   │

Step 1: Request Upload URL

POST /api/workspaces/{workspace_id}/documents/upload/
{
  "filename": "product-guide.pdf",
  "content_type": "application/pdf",
  "file_size": 1234567,
  "name": "Product Guide",
  "description": "Internal product documentation",
  "knowledge_base_ids": ["uuid1"]
}

Validations:

  • Filename: required, max 500 chars
  • File size: min 1 byte, max 50MB (configurable via DOCUMENT_MAX_UPLOAD_SIZE)
  • Extension: must be in DOCUMENT_ALLOWED_EXTENSIONS

Step 2: Receive Presigned URL

{
  "document_id": "uuid",
  "upload_url": "https://bucket.s3.region.amazonaws.com/documents/ws-id/uuid/product-guide.pdf?X-Amz-...",
  "s3_key": "documents/ws-id/uuid/product-guide.pdf"
}

The server creates a Document record with processing_status="pending" and generates a presigned PUT URL valid for 1 hour.

Step 3: Upload to S3

The client uploads the file directly to S3 using the presigned URL:

await fetch(uploadUrl, {
  method: "PUT",
  body: file,
  headers: { "Content-Type": contentType },
});

Step 4: Confirm Upload

POST /api/workspaces/{workspace_id}/documents/{document_id}/confirm-upload/

This triggers the Celery ingestion task. The document status changes to "processing".

Step 5: Poll Status

GET /api/workspaces/{workspace_id}/documents/{document_id}/processing-status/

Poll until status is SUCCESS or FAILURE.

S3 Key Generation

Keys follow a predictable pattern ensuring uniqueness:

{prefix}/{workspace_id}/{uuid}/{sanitized_filename}

Example: documents/ws-abc123/doc-def456/product-guide.pdf

The UUID ensures that duplicate filenames within the same workspace don't collide.

Download URLs

Generate a temporary download URL for the original file:

GET /api/workspaces/{workspace_id}/documents/{document_id}/download-url/
{
  "download_url": "https://bucket.s3.region.amazonaws.com/documents/...?X-Amz-..."
}

Presigned GET URLs expire after 1 hour (configurable via DOCUMENT_PRESIGNED_URL_EXPIRY).

Deletion

When a document is deleted via the API:

  1. All DocumentChunk rows are cascade-deleted
  2. The S3 object is deleted (gracefully handles S3 errors)
  3. The Document record is removed

Configuration

SettingDefaultDescription
AWS_ACCESS_KEY_IDAWS credentials
AWS_SECRET_ACCESS_KEYAWS credentials
AWS_STORAGE_BUCKET_NAMES3 bucket name
AWS_S3_REGION_NAMEAWS region
DOCUMENT_S3_PREFIXdocumentsKey prefix in bucket
DOCUMENT_PRESIGNED_URL_EXPIRY3600URL TTL in seconds
DOCUMENT_MAX_UPLOAD_SIZE52428800Max file size (50MB)
DOCUMENT_ALLOWED_EXTENSIONSpdf,docx,doc,txt,csv,md,pptx,xlsxAllowed types

If AWS credentials are not configured, the upload endpoint raises S3NotConfiguredError.

On this page