- name
- sc-upload
- description
- Upload a file to the Content Search backend and poll the ingestion task until the file is fully indexed (status COMPLETED). Handles duplicate detection (code 40901), cleanup-and-retry, and task timeout. Supported file types: pdf, txt, docx, doc, pptx, ppt, xlsx, xls, jpg, jpeg, png, mp4, avi, mov, mkv. Use when the user says "upload a file", "ingest a document", "upload pdf", "index a file", "add course material", "upload video", "upload image", or "ingest content".
# SC Upload
Upload a file to the Content Search backend and wait for ingestion to complete.
**Agent: execute every command below directly using your terminal tool and relay
the output.** Endpoints use the base URL `http://127.0.0.1:9011`.
Set `$BASE = "http://127.0.0.1:9011"` for all snippets.
---
## Preconditions
### Set corporate proxy (required for any outbound download; localhost API calls bypass it)
Probe health first — if the backend is unreachable, use
[`sc-doctor`](../sc-doctor/SKILL.md) / [`sc-up`](../sc-up/SKILL.md):
```powershell
$BASE = "http://127.0.0.1:9011"
# 200 = all services ready; 503 = degraded, body names the failing one
try { (Invoke-WebRequest -Uri "$BASE/api/v1/system/health" -UseBasicParsing).Content }
catch { $_.ErrorDetails.Message }
```
The file must be one of the supported extensions:
`pdf`, `txt`, `docx`, `doc`, `pptx`, `ppt`, `xlsx`, `xls`,
`jpg`, `jpeg`, `png`, `mp4`, `avi`, `mov`, `mkv`.
---
## 1. Upload and trigger ingestion
**🤖 Agent instruction:** Before executing the command below, use the `vscode_askQuestions` tool to:
1. **Get the file path** to upload (user must provide full path)
2. **Optionally ask for tags** (comma-separated, e.g., "knowledge,ai,tutorial")
`POST /api/v1/object/upload-ingest` is a multipart form request with two fields:
- `file` — the binary file
- `meta` — a JSON string with optional metadata (tags, description)
See [`references/upload-request.md`](./references/upload-request.md) for the
full `meta` schema.
```powershell
$BASE = "http://127.0.0.1:9011"
# Agent: Set $FilePath to the user-provided file path from ask_user
$FilePath = "<USER_PROVIDED_FILE_PATH>"
# Agent: Set $Tags to user-provided tags (or empty string if none)
$Tags = "<USER_PROVIDED_TAGS_OR_EMPTY>"
# Determine file type from extension
$extension = [System.IO.Path]::GetExtension($FilePath).TrimStart('.').ToLower()
$fileType = switch ($extension) {
{ $_ -in @('pdf','txt','docx','doc','pptx','ppt','xlsx','xls') } { "document" }
{ $_ -in @('jpg','jpeg','png') } { "image" }
{ $_ -in @('mp4','avi','mov','mkv') } { "video" }
default { "document" }
}
$fileName = [System.IO.Path]::GetFileName($FilePath)
# Manually construct JSON to ensure tags is always an array (not a string)
if ($Tags) {
$tagsList = ($Tags -split ',' | ForEach-Object { "`"$($_.Trim())`"" }) -join ","
$meta = "{`"file_name`":`"$fileName`",`"type`":`"$fileType`",`"tags`":[$tagsList]}"
} else {
$meta = "{`"file_name`":`"$fileName`",`"type`":`"$fileType`"}"
}
# Build multipart form and POST
Add-Type -AssemblyName System.Net.Http
$client = [System.Net.Http.HttpClient]::new()
$content = [System.Net.Http.MultipartFormDataContent]::new()
$fileBytes = [System.IO.File]::ReadAllBytes($FilePath)
$fileContent = [System.Net.Http.ByteArrayContent]::new($fileBytes)
$fileContent.Headers.ContentType =
[System.Net.Http.Headers.MediaTypeHeaderValue]::Parse("application/octet-stream")
$content.Add($fileContent, "file", $fileName)
$content.Add([System.Net.Http.StringContent]::new($meta), "meta")
$response = $client.PostAsync("$BASE/api/v1/object/upload-ingest", $content).Result
$body = $response.Content.ReadAsStringAsync().Result | ConvertFrom-Json
$body | ConvertTo-Json -Depth 5
```
**Expected response:**
```json
{
"code": 20000,
"data": {
"task_id": "<TASK_ID>",
"status": "PROCESSING",
"file_key": "<FILE_KEY>"
},
"message": "Success",
"timestamp": 1234567890
}
```
> **Duplicate detection:** if `code == 40901`, the file already exists (detected by SHA256 hash). The response includes the existing `task_id` and `file_hash`. Re-upload is allowed if the previous task status is `FAILED`. Go to step 1b (cleanup and retry) or skip directly to step 2 to poll the existing task.
### 1b. Handle duplicate (code 40901)
The cleanup endpoint deletes:
- Entire run directory: `runs/{run_id}/` (raw files, derived files, OCR outputs)
- ChromaDB vector index entries
- FileAsset database record
- AITask database record
```powershell
# Agent: Extract task_id from the 40901 response ($body.data.task_id)
$TASK_ID = $body.data.task_id
Invoke-WebRequest -Uri "$BASE/api/v1/object/cleanup-task/$TASK_ID" `
-Method Delete -UseBasicParsing
# Now retry the upload from step 1
```
> [!NOTE]
> Cleanup fails if the task status is `PROCESSING`. Wait for completion or failure first.
---
## 2. Poll task status until complete
Poll `GET /api/v1/task/query/{task_id}` every 3 seconds.
Terminal statuses are `COMPLETED` and `FAILED`.
> [!NOTE]
> The `progress` field is always 100 (hardcoded) and is not a real progress indicator. Status transitions are: `QUEUED` → `PROCESSING` → `COMPLETED`/`FAILED`.
```powershell
# Agent: Extract $TASK_ID from the response in step 1 ($body.data.task_id)
$TASK_ID = $body.data.task_id
$deadline = (Get-Date).AddMinutes(10)
do {
Start-Sleep -Seconds 3
$r = Invoke-WebRequest -Uri "$BASE/api/v1/task/query/$TASK_ID" `
-UseBasicParsing
$task = ($r.Content | ConvertFrom-Json).data
Write-Host "[$([datetime]::Now.ToString('HH:mm:ss'))] status=$($task.status) progress=$($task.progress)"
if ($task.status -in @("COMPLETED","FAILED")) { break }
} while ((Get-Date) -lt $deadline)
Write-Host "Final status: $($task.status)"
```
- **`COMPLETED`** → file is indexed and ready for Q&A. Note the `file_key` for deletion later. If the file is a PDF and OCR is enabled, the result will include `ocr_text_key`. If video summarization was requested, result includes `video_summary` and `video_summary_status`.
- **`FAILED`** → ingestion error. Read `task.result.error` for the reason; check backend logs with `sc-doctor`. Failed tasks trigger automatic cleanup of FileAsset, physical file, and ChromaDB entries.
- **Timeout (10 min)** → the backend is overloaded or stalled. Check `sc-doctor`.
---
## 3. Confirm the file appears in the index
```powershell
$r = Invoke-WebRequest -Uri "$BASE/api/v1/object/files/list?page=1&page_size=20" `
-UseBasicParsing
$files = ($r.Content | ConvertFrom-Json).data.files
$files | Select-Object file_name, @{N="type";E={$_.meta.type}}, @{N="vectors";E={$_.index.vector_count}}, status |
Format-Table -AutoSize
```
**Note**: The file is searchable when the task status is `COMPLETED`. Vector indexing may
take a few additional seconds to appear in the list, but the task completion is the
source of truth for searchability.
---
## 4. Advanced Backend Features
### OCR Processing (PDFs)
When `OCR_ENABLED=true` (environment variable), PDF files are automatically processed:
1. External OCR service is called at `http://127.0.0.1:8000` (timeout: 120s)
2. Extracted text is saved as `.ocr.txt` file in the same run directory
3. Vector indexing uses the OCR text file instead of the original PDF
4. The `task.result` includes `ocr_text_key` pointing to the extracted text
### Video Summarization
When `VIDEO_SUMMARIZATION_ENABLED=true` (default), videos can be summarized:
1. Pass `vs_enabled: true` in the `meta` object to enable per-file
2. Optionally provide `prompt` and `chunk_duration` in the upload request
3. Summarization runs AFTER the task is marked `COMPLETED` (file is already searchable)
4. The `task.result` includes `video_summary` and `video_summary_status`
5. Generated summaries are stored in `runs/{run_id}/derived/`
### File Integrity Validation
PDF and video files (PDF, MP4, AVI, MOV, MKV) undergo integrity validation:
- PDF: checked for valid structure
- Video: validated for proper format and codec
- Corrupted files result in FAILED task with `error_type: "corrupted_file"`
### Automatic Cleanup on Failure
When indexing fails, the backend automatically cleans up:
- FileAsset database record
- Physical file in storage
- ChromaDB vector index entries
This prevents orphaned data and allows re-upload with the same file.
---
## Response Codes Reference
| Code | Meaning | Action |
|---|---|---|
| `20000` | Success | Continue to next step |
| `40000` | Bad request | Check request parameters |
| `40002` | Invalid file | File failed validation (unsupported type) |
| `40901` | File already exists (duplicate) | Cleanup and retry, or poll existing task |
| `41301` | File too large | Reduce file size or increase backend limits |
| `50002` | Task not found | Task may have expired or been deleted |
| `50003` | Process failed | Check backend logs for details |
---
## Troubleshooting
| Symptom | Likely cause | Action |
|---|---|---|
| `code: 40901` | File already exists (duplicate hash) | Cleanup task (step 1b) then retry, or skip to step 2 if re-uploading after FAILED task |
| `code: 40002` | Invalid/unsupported file type | Check file extension against allowed list; convert to supported format |
| `code: 41301` | File too large | Default limits: documents 100MB, videos 1024MB; check/update env vars `DOCUMENT_MAX_MB`, `VIDEO_MAX_MB` |
| `FAILED` status | Ingestion pipeline error | Check `task.result.error`; check backend logs via `sc-doctor` |
| Timeout after 10 min | Backend overloaded or stalled | Restart backend (`sc-up`); reduce file size |
| Connection reset during upload | Large video upload timeout | Backend accepts large files; check network/firewall settings |
| Corrupted file error | File integrity check failed | Re-download or re-export the file; ensure proper encoding |
| OCR timeout | OCR service unavailable | Ensure OCR service is running at port 8000; check `OCR_ENABLED` env var |
---
## Output
Report: **task_id** → **status polling log** → **final `COMPLETED`** →
file appears in `GET /api/v1/object/files/list`.
**Note**: Task `COMPLETED` status means the file is searchable. Vector counts in the file
list may take a few seconds to update, but searchability is determined by task completion.
GitHubで見る