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

# Upload File

> Upload and index documents from paths visible inside the server container (async file upload job).

## Overview

Upload one or more files from a **server-visible path** into a **text** namespace. The server reads the file from disk (no HTTP multipart body), chunks it with the bundled Python chunker, generates batch summaries every 100 chunks, embeds each chunk, and stores them for search.

<Note>
  **Path-based upload only (v1).** Copy or mount files under `~/.moorcheh/uploads` on the host (mounted read-only as `/uploads` in the container). API requests must use **container paths** such as `/uploads/document.pdf`.
</Note>

Supported extensions: **`.pdf`**, **`.docx`**, **`.xlsx`**, **`.pptx`**, **`.txt`**, **`.csv`**, **`.md`**, **`.json`**.

The job runs asynchronously. Poll [File job status](/on-prem/api-references/files/file-job-status) with the returned `job_id`.

## Path parameters

<ParamField path="namespace_name" type="string" required>
  Target text namespace.
</ParamField>

## Headers

<ParamField header="Content-Type" type="string" required>
  Must be `application/json`
</ParamField>

## Body

<ParamField body="files" type="array" required>
  Non-empty array of file objects.
</ParamField>

<ParamField body="files[].path" type="string" required>
  Absolute path **inside the server container**. Use `/uploads/...` for files under the default upload mount.
</ParamField>

<ParamField body="files[].force_reindex" type="boolean" default="false">
  When `true`, re-chunk and replace the index even if `file_size` and `file_mtime` match a previous upload.
</ParamField>

<ParamField body="files[].{metadata}" type="any">
  Optional extra keys on each file object are merged into chunk metadata (for example `"department": "engineering"`).
</ParamField>

## File identity and deduplication

* **`file_id`** is server-generated: first 16 hex chars of `sha256(namespace + NUL + absolute_path)`.
* **Skip re-upload** when the same path already exists with the same `file_size` and `file_mtime`, unless `force_reindex` is `true`.
* Chunk ids: `{file_id}_chunk_{index}`. Summary ids: `{file_id}_summary_{batch}`.
* Each content chunk metadata includes `summary_chunk_id` (bare id, e.g. `abc_summary_0`) linking to its batch summary.

## Request example

```bash theme={null}
curl -X POST "http://localhost:8080/namespaces/my-documents/files" \
  -H "Content-Type: application/json" \
  -d '{
    "files": [
      {
        "path": "/uploads/document.pdf",
        "department": "engineering",
        "force_reindex": false
      }
    ]
  }'
```

## Response fields

<ResponseField name="status" type="string">
  `"success"` when the upload job started or all files were skipped.
</ResponseField>

<ResponseField name="message" type="string">
  Human-readable result.
</ResponseField>

<ResponseField name="job_id" type="string">
  Async job id when at least one file is uploading. Poll [File job status](/on-prem/api-references/files/file-job-status).
</ResponseField>

<ResponseField name="namespace_name" type="string">
  Target namespace.
</ResponseField>

<ResponseField name="total" type="number">
  Number of file entries in the request.
</ResponseField>

<ResponseField name="ingesting" type="number">
  Files accepted into the upload job.
</ResponseField>

<ResponseField name="skipped" type="array">
  Files skipped as already indexed (same size/mtime). Each entry includes `file_id`, `absolute_path`, and `message`.
</ResponseField>

<ResponseExample>
  ```json 202 - Upload started theme={null}
  {
    "status": "success",
    "message": "File upload started. Poll file job status for progress.",
    "job_id": "job-1fad681df00046c0a7f950daeb52f120",
    "namespace_name": "my-documents",
    "total": 1,
    "ingesting": 1,
    "skipped": []
  }
  ```

  ```json 200 - All skipped theme={null}
  {
    "status": "success",
    "message": "All files already indexed.",
    "namespace_name": "my-documents",
    "skipped": [
      {
        "file_id": "a1b2c3d4e5f67890",
        "absolute_path": "/uploads/document.pdf",
        "message": "File already indexed."
      }
    ]
  }
  ```
</ResponseExample>

## Errors

| HTTP | Cause                                                                                             |
| ---- | ------------------------------------------------------------------------------------------------- |
| 400  | Empty `files`, unsupported extension, path outside `/uploads`, file not found, non-text namespace |
| 404  | Namespace not found                                                                               |

Item limit is checked **during the upload job** (not at `POST` time). If exceeded, the job finishes with `status: "failed"` and `last_error` like `"Item limit exceeded: max …, current …, requested … new items."` — poll [File job status](/on-prem/api-references/files/file-job-status).

## Important notes

<Warning>
  * Use **container paths** (`/uploads/...`) in direct API calls — not Windows or macOS host paths.
  * The CLI `moorcheh upload-file` accepts a host path under `~/.moorcheh/uploads` and converts it for you.
  * Upload indexes content only; deleting the index later does **not** remove the file on disk.
</Warning>

## Related

* [List files](/on-prem/api-references/files/list)
* [Delete file index](/on-prem/api-references/files/delete)
* [File job status](/on-prem/api-references/files/file-job-status)
* [CLI: moorcheh upload-file](/on-prem/cli/files/upload-file)
* [Python: files.upload()](/on-prem/python-client/files/upload-files)
