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

# Upload Documents

> Upload documents for extraction processing

## Endpoint

```
POST /upload
```

Upload one or more documents to receive document IDs for extraction. Upload stores the files; schema generation and extraction perform document conversion later.

## Authentication

<ParamField header="X-API-Key" type="string" required>
  API key for authentication. Your unique API key.
</ParamField>

## Request Parameters

<ParamField body="files" type="file[]" required>
  One or more document files to upload.

  Extraction-compatible formats:

  * `application/pdf` (.pdf)
  * `image/jpeg` (.jpg, .jpeg)
  * `image/png` (.png)
  * `image/tiff` (.tiff)
  * `image/bmp` (.bmp)

  Other file types may upload, but schema generation and extraction can fail if the backend cannot convert the file.
</ParamField>

## Response

<ResponseField name="document_ids" type="string[]">
  Array of UUID v4 document identifiers. Use these IDs for extraction requests.
</ResponseField>

## Examples

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.documind.cloud/api/v1/upload \
    -H 'X-API-Key: YOUR_API_KEY' \
    -F 'files=@invoice1.pdf' \
    -F 'files=@invoice2.pdf' \
    -F 'files=@receipt.jpg'
  ```

  ```python Python theme={null}
  import requests

  API_KEY = "your_api_key_here"
  headers = {"X-API-Key": API_KEY}

  # Upload single file
  with open("invoice.pdf", "rb") as f:
      files = {"files": f}
      response = requests.post(
          "https://api.documind.cloud/api/v1/upload",
          headers=headers,
          files=files
      )

  document_ids = response.json()
  print(f"Uploaded document ID: {document_ids[0]}")

  # Upload multiple files
  files_to_upload = [
      ("files", open("doc1.pdf", "rb")),
      ("files", open("doc2.pdf", "rb")),
      ("files", open("doc3.jpg", "rb"))
  ]

  response = requests.post(
      "https://api.documind.cloud/api/v1/upload",
      headers=headers,
      files=files_to_upload
  )

  # Close file handles
  for _, file_handle in files_to_upload:
      file_handle.close()

  document_ids = response.json()
  print(f"Uploaded {len(document_ids)} documents")
  ```

  ```javascript Node.js theme={null}
  const fs = require('fs');
  const FormData = require('form-data');

  // Upload single file
  const formData = new FormData();
  formData.append('files', fs.createReadStream('invoice.pdf'));

  const response = await fetch('https://api.documind.cloud/api/v1/upload', {
    method: 'POST',
    headers: {
      'X-API-Key': process.env.API_KEY,
      ...formData.getHeaders()
    },
    body: formData
  });

  const documentIds = await response.json();
  console.log(`Uploaded document ID: ${documentIds[0]}`);

  // Upload multiple files
  const multiForm = new FormData();
  multiForm.append('files', fs.createReadStream('doc1.pdf'));
  multiForm.append('files', fs.createReadStream('doc2.pdf'));
  multiForm.append('files', fs.createReadStream('doc3.jpg'));

  const multiResponse = await fetch('https://api.documind.cloud/api/v1/upload', {
    method: 'POST',
    headers: {
      'X-API-Key': process.env.API_KEY,
      ...multiForm.getHeaders()
    },
    body: multiForm
  });

  const multiDocIds = await multiResponse.json();
  console.log(`Uploaded ${multiDocIds.length} documents`);
  ```
</CodeGroup>

<ResponseExample>
  ```json Success Response (Single File) theme={null}
  [
    "550e8400-e29b-41d4-a716-446655440000"
  ]
  ```

  ```json Success Response (Multiple Files) theme={null}
  [
    "550e8400-e29b-41d4-a716-446655440000",
    "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
    "7c9e6679-7425-40de-944b-e07fc1f90ae7"
  ]
  ```
</ResponseExample>

## File Size & Limits

<Note>
  The backend does not enforce a documented per-file or per-request count limit in application code. Infrastructure may still reject oversized requests before they reach the backend. For larger batches, split files into multiple upload requests and retry failed requests.
</Note>

## Storage Duration

No public retention SLA is exposed by the backend. Keep your own source documents if you need long-term archival.

## Processing Time

The API returns document IDs after the files are stored. OCR, layout analysis, and model processing happen later during schema generation or extraction; the backend does not expose a public upload timing SLA.

## Error Responses

### 400 Bad Request

Malformed multipart request:

```json theme={null}
{
  "detail": "There was an error parsing the body"
}
```

**Common causes**:

* Missing `files` form field
* Invalid multipart body
* Corrupt upload stream

### 413 Payload Too Large

Request exceeds size limits:

```json theme={null}
{
  "detail": "Request Entity Too Large"
}
```

**Solution**: Split your batch into smaller requests.

### 500 Internal Server Error

Server-side processing error:

```json theme={null}
{
  "detail": "Failed to upload documents. Please try again later."
}
```

**Solution**: Retry the request. If the error persists, contact support.

## Best Practices

<AccordionGroup>
  <Accordion title="Batch Upload for Efficiency">
    Upload multiple documents in a single request to reduce API calls:

    ```python theme={null}
    # ✓ Good: Batch upload
    files = [("files", open(f, "rb")) for f in file_paths]
    response = requests.post(url, headers=headers, files=files)

    # ✗ Avoid: Multiple single uploads
    for file_path in file_paths:
        response = requests.post(url, headers=headers, 
                               files={"files": open(file_path, "rb")})
    ```
  </Accordion>

  <Accordion title="Handle File Handles Properly">
    Always close file handles after upload:

    ```python theme={null}
    # Using context manager (recommended)
    with open("document.pdf", "rb") as f:
        response = requests.post(url, files={"files": f})

    # Or explicitly close
    files = [("files", open(f, "rb")) for f in file_paths]
    try:
        response = requests.post(url, files=files)
    finally:
        for _, fh in files:
            fh.close()
    ```
  </Accordion>

  <Accordion title="Validate Files Before Upload">
    Check file format before uploading:

    ```python theme={null}
    import os

    SUPPORTED = {'.pdf', '.jpg', '.jpeg', '.png', '.tiff', '.bmp'}

    def validate_file(file_path):
        ext = os.path.splitext(file_path)[1].lower()
        if ext not in SUPPORTED:
            raise ValueError(f"Unsupported format: {ext}")
        
        return True

    # Validate before upload
    valid_files = [f for f in file_paths if validate_file(f)]
    ```
  </Accordion>

  <Accordion title="Store Document IDs">
    Map document IDs to original filenames for tracking:

    ```python theme={null}
    # Create mapping
    filename_to_id = {}

    for file_path in file_paths:
        with open(file_path, "rb") as f:
            response = requests.post(url, files={"files": f})
            doc_id = response.json()[0]
            filename_to_id[os.path.basename(file_path)] = doc_id

    # Save mapping for later reference
    import json
    with open("document_mapping.json", "w") as f:
        json.dump(filename_to_id, f, indent=2)
    ```
  </Accordion>
</AccordionGroup>

## Next Steps

After uploading documents, you can:

<CardGroup cols={2}>
  <Card title="Generate Schema" icon="table" href="/api-reference/generate-schema">
    Auto-generate extraction schema from a sample document
  </Card>

  <Card title="Extract Data" icon="file-export" href="/api/extraction/extract-data">
    Extract structured data using a schema
  </Card>

  <Card title="List Documents" icon="list" href="/api-reference/list-documents">
    View documents with extraction records
  </Card>
</CardGroup>
