Upload File
curl --request POST \
--url http://localhost:8080/namespaces/{namespace_name}/files \
--header 'Content-Type: <content-type>' \
--data '
{
"files": [
{}
],
"files[].path": "<string>",
"files[].force_reindex": true,
"files[].{metadata}": "<any>"
}
'import requests
url = "http://localhost:8080/namespaces/{namespace_name}/files"
payload = {
"files": [{}],
"files[].path": "<string>",
"files[].force_reindex": True,
"files[].{metadata}": "<any>"
}
headers = {"Content-Type": "<content-type>"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': '<content-type>'},
body: JSON.stringify({
files: [{}],
'files[].path': '<string>',
'files[].force_reindex': true,
'files[].{metadata}': '<any>'
})
};
fetch('http://localhost:8080/namespaces/{namespace_name}/files', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_PORT => "8080",
CURLOPT_URL => "http://localhost:8080/namespaces/{namespace_name}/files",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'files' => [
[
]
],
'files[].path' => '<string>',
'files[].force_reindex' => true,
'files[].{metadata}' => '<any>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: <content-type>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "http://localhost:8080/namespaces/{namespace_name}/files"
payload := strings.NewReader("{\n \"files\": [\n {}\n ],\n \"files[].path\": \"<string>\",\n \"files[].force_reindex\": true,\n \"files[].{metadata}\": \"<any>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "<content-type>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("http://localhost:8080/namespaces/{namespace_name}/files")
.header("Content-Type", "<content-type>")
.body("{\n \"files\": [\n {}\n ],\n \"files[].path\": \"<string>\",\n \"files[].force_reindex\": true,\n \"files[].{metadata}\": \"<any>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("http://localhost:8080/namespaces/{namespace_name}/files")
http = Net::HTTP.new(url.host, url.port)
request = Net::HTTP::Post.new(url)
request["Content-Type"] = '<content-type>'
request.body = "{\n \"files\": [\n {}\n ],\n \"files[].path\": \"<string>\",\n \"files[].force_reindex\": true,\n \"files[].{metadata}\": \"<any>\"\n}"
response = http.request(request)
puts response.read_body{
"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": []
}
{
"status": "success",
"message": "All files already indexed.",
"namespace_name": "my-documents",
"skipped": [
{
"file_id": "a1b2c3d4e5f67890",
"absolute_path": "/uploads/document.pdf",
"message": "File already indexed."
}
]
}
File storage
Upload File
Upload and index documents from paths visible inside the server container (async file upload job).
POST
/
namespaces
/
{namespace_name}
/
files
Upload File
curl --request POST \
--url http://localhost:8080/namespaces/{namespace_name}/files \
--header 'Content-Type: <content-type>' \
--data '
{
"files": [
{}
],
"files[].path": "<string>",
"files[].force_reindex": true,
"files[].{metadata}": "<any>"
}
'import requests
url = "http://localhost:8080/namespaces/{namespace_name}/files"
payload = {
"files": [{}],
"files[].path": "<string>",
"files[].force_reindex": True,
"files[].{metadata}": "<any>"
}
headers = {"Content-Type": "<content-type>"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': '<content-type>'},
body: JSON.stringify({
files: [{}],
'files[].path': '<string>',
'files[].force_reindex': true,
'files[].{metadata}': '<any>'
})
};
fetch('http://localhost:8080/namespaces/{namespace_name}/files', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_PORT => "8080",
CURLOPT_URL => "http://localhost:8080/namespaces/{namespace_name}/files",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'files' => [
[
]
],
'files[].path' => '<string>',
'files[].force_reindex' => true,
'files[].{metadata}' => '<any>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: <content-type>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "http://localhost:8080/namespaces/{namespace_name}/files"
payload := strings.NewReader("{\n \"files\": [\n {}\n ],\n \"files[].path\": \"<string>\",\n \"files[].force_reindex\": true,\n \"files[].{metadata}\": \"<any>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "<content-type>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("http://localhost:8080/namespaces/{namespace_name}/files")
.header("Content-Type", "<content-type>")
.body("{\n \"files\": [\n {}\n ],\n \"files[].path\": \"<string>\",\n \"files[].force_reindex\": true,\n \"files[].{metadata}\": \"<any>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("http://localhost:8080/namespaces/{namespace_name}/files")
http = Net::HTTP.new(url.host, url.port)
request = Net::HTTP::Post.new(url)
request["Content-Type"] = '<content-type>'
request.body = "{\n \"files\": [\n {}\n ],\n \"files[].path\": \"<string>\",\n \"files[].force_reindex\": true,\n \"files[].{metadata}\": \"<any>\"\n}"
response = http.request(request)
puts response.read_body{
"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": []
}
{
"status": "success",
"message": "All files already indexed.",
"namespace_name": "my-documents",
"skipped": [
{
"file_id": "a1b2c3d4e5f67890",
"absolute_path": "/uploads/document.pdf",
"message": "File already indexed."
}
]
}
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.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..pdf, .docx, .xlsx, .pptx, .txt, .csv, .md, .json.
The job runs asynchronously. Poll File job status with the returned job_id.
Path parameters
string
required
Target text namespace.
Headers
string
required
Must be
application/jsonBody
array
required
Non-empty array of file objects.
string
required
Absolute path inside the server container. Use
/uploads/... for files under the default upload mount.boolean
default:"false"
When
true, re-chunk and replace the index even if file_size and file_mtime match a previous upload.any
Optional extra keys on each file object are merged into chunk metadata (for example
"department": "engineering").File identity and deduplication
file_idis server-generated: first 16 hex chars ofsha256(namespace + NUL + absolute_path).- Skip re-upload when the same path already exists with the same
file_sizeandfile_mtime, unlessforce_reindexistrue. - 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
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
string
"success" when the upload job started or all files were skipped.string
Human-readable result.
string
Async job id when at least one file is uploading. Poll File job status.
string
Target namespace.
number
Number of file entries in the request.
number
Files accepted into the upload job.
array
Files skipped as already indexed (same size/mtime). Each entry includes
file_id, absolute_path, and message.{
"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": []
}
{
"status": "success",
"message": "All files already indexed.",
"namespace_name": "my-documents",
"skipped": [
{
"file_id": "a1b2c3d4e5f67890",
"absolute_path": "/uploads/document.pdf",
"message": "File already indexed."
}
]
}
Errors
| HTTP | Cause |
|---|---|
| 400 | Empty files, unsupported extension, path outside /uploads, file not found, non-text namespace |
| 404 | Namespace not found |
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.
Important notes
- Use container paths (
/uploads/...) in direct API calls — not Windows or macOS host paths. - The CLI
moorcheh upload-fileaccepts a host path under~/.moorcheh/uploadsand converts it for you. - Upload indexes content only; deleting the index later does not remove the file on disk.
Related
Was this page helpful?