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

# Upload documents

> Upload files through a presigned URL and wait for them to become searchable.

File ingestion uses a presigned upload flow: create a document record, upload the bytes directly to object storage, then poll until background processing finishes.

## Before you start

Set your API key, tenant, knowledge base, and file details:

```bash theme={null}
export AUTOSAGE_KEY="sk_live_your_key_here"
export AUTOSAGE_URL="https://api.autosage.ai/api/v1"
export TENANT_ID="your_tenant_id"
export KB_ID="your_knowledge_base_id"
export FILE_PATH="./handbook.pdf"
export MIME_TYPE="application/pdf"
```

The examples below use `jq` to read the JSON response.

## Presigned upload flow

<Steps>
  <Step title="Request an upload URL">
    Send the destination knowledge base, tenant, filename, MIME type, and exact byte size.

    ```bash theme={null}
    FILE_SIZE=$(wc -c < "$FILE_PATH" | tr -d ' ')
    FILE_NAME=$(basename "$FILE_PATH")

    PRESIGN_RESPONSE=$(curl --fail --silent --show-error \
      -X POST "$AUTOSAGE_URL/documents/presign" \
      -H "Authorization: Bearer $AUTOSAGE_KEY" \
      -H "Content-Type: application/json" \
      -d "$(jq -n \
        --arg kb_id "$KB_ID" \
        --arg tenant_id "$TENANT_ID" \
        --arg filename "$FILE_NAME" \
        --arg mime_type "$MIME_TYPE" \
        --argjson size_bytes "$FILE_SIZE" \
        '{kb_id: $kb_id, tenant_id: $tenant_id, filename: $filename, mime_type: $mime_type, size_bytes: $size_bytes}')")

    DOCUMENT_ID=$(printf '%s' "$PRESIGN_RESPONSE" | jq -r '.document_id')
    UPLOAD_URL=$(printf '%s' "$PRESIGN_RESPONSE" | jq -r '.upload_url')

    printf 'Document: %s\n' "$DOCUMENT_ID"
    ```

    The response has this shape:

    ```json theme={null}
    {
      "document_id": "2e7a1aa0-...",
      "upload_url": "https://...",
      "expires_at": "2026-07-10T12:00:00.000Z"
    }
    ```
  </Step>

  <Step title="Upload the file bytes">
    Put the file at `upload_url` with the same `Content-Type` used in the presign request. The storage request does not use your AutoSage bearer key.

    ```bash theme={null}
    curl --fail --silent --show-error \
      -X PUT "$UPLOAD_URL" \
      -H "Content-Type: $MIME_TYPE" \
      --data-binary "@$FILE_PATH"
    ```

    Processing starts automatically after the upload arrives.
  </Step>

  <Step title="Poll until processing finishes">
    ```bash theme={null}
    while true; do
      STATUS_RESPONSE=$(curl --fail --silent --show-error \
        "$AUTOSAGE_URL/documents/status?document_id=$DOCUMENT_ID" \
        -H "Authorization: Bearer $AUTOSAGE_KEY")

      STATUS=$(printf '%s' "$STATUS_RESPONSE" | jq -r '.status')
      printf 'Status: %s\n' "$STATUS"

      case "$STATUS" in
        processed) break ;;
        failed) printf '%s\n' "$STATUS_RESPONSE" >&2; exit 1 ;;
        *) sleep 3 ;;
      esac
    done
    ```
  </Step>
</Steps>

<Warning>
  Treat `upload_url` as a short-lived secret and use it before `expires_at`. Upload the bytes exactly once with the intended MIME type; request a new URL if the original one expires.
</Warning>

## Processing statuses

| Status           | Meaning                                                                           |
| ---------------- | --------------------------------------------------------------------------------- |
| `pending_upload` | The document record exists and is waiting for file bytes                          |
| `queued`         | The upload arrived and background processing is queued                            |
| `processing`     | The file is being parsed, chunked, embedded, and indexed                          |
| `processed`      | The document is searchable                                                        |
| `failed`         | Processing ended unsuccessfully; inspect the document and reprocess or replace it |

AutoSage supports PDFs, including scanned PDFs; plain text, Markdown, HTML, CSV, and JSON; Word, Excel, and PowerPoint files; common audio formats; and common image formats. Files can be up to 500 MB, subject to the tenant's document-count and storage quotas.

<Note>
  Quota checks happen before the upload URL is issued. A tenant over its document or storage allowance receives a `403` response with a clear message.
</Note>

## URL ingestion and document maintenance

| Method   | Path                         | Purpose                                                      |
| -------- | ---------------------------- | ------------------------------------------------------------ |
| `POST`   | `/documents/upload-url`      | Scrape one URL and queue it as a document                    |
| `POST`   | `/documents/upload-url-site` | Discover up to about 25 pages from a site before ingestion   |
| `POST`   | `/documents/:id/reprocess`   | Queue an existing document for processing again              |
| `GET`    | `/documents/:id/download`    | Create a temporary download URL                              |
| `DELETE` | `/documents/:id`             | Remove the document from search and clean up its stored data |

<Tip>
  Wait for `processed` before querying the knowledge base. Remove failed or abandoned uploads you no longer need because their document records and stored bytes count toward tenant quotas.
</Tip>

For the complete background pipeline, see [Ingestion lifecycle](/developers/lifecycles/ingestion). For endpoint schemas, see the [Documents API](/developers/api/documents).

## Next steps

<CardGroup cols={2}>
  <Card title="Ingestion lifecycle" icon="arrows-rotate" href="/developers/lifecycles/ingestion">
    Follow parsing, chunking, embedding, indexing, and cleanup.
  </Card>

  <Card title="RAG lifecycle" icon="magnifying-glass" href="/developers/lifecycles/rag">
    See how processed content becomes cited evidence.
  </Card>
</CardGroup>
