Complete REST API documentation for hfdownloader serve.
Start the server:
hfdownloader serve --port 8080The server provides:
- REST API for download management
- WebSocket for real-time progress updates
- Web UI at the root URL
- Request bodies:
application/json - Response bodies:
application/json - WebSocket messages: JSON
Enable with server flags:
hfdownloader serve --auth-user admin --auth-pass secret123All requests require the Authorization header:
Authorization: Basic YWRtaW46c2VjcmV0MTIzFor private/gated models, provide via:
- Server flag:
hfdownloader serve -t hf_xxxxx - Settings API:
POST /api/settings
http://localhost:8080/api
Check server status.
Response 200 OK
{
"status": "ok",
"version": "3.0.0",
"time": "2024-01-15T10:30:00Z"
}Example
curl http://localhost:8080/api/healthStart a new download job.
Request Body
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
repo |
string | Yes | Repository ID (owner/name) | |
revision |
string | No | main |
Branch, tag, or commit |
dataset |
boolean | No | false |
Treat as dataset |
filters |
string[] | No | [] |
File filter patterns |
excludes |
string[] | No | [] |
Exclude patterns |
appendFilterSubdir |
boolean | No | false |
Create filter subdirs |
dryRun |
boolean | No | false |
Plan only |
Filter Syntax
Filters can be embedded in repo name:
{ "repo": "TheBloke/Mistral-7B-Instruct-v0.2-GGUF:q4_k_m,q5_k_m" }Or as separate field:
{
"repo": "TheBloke/Mistral-7B-Instruct-v0.2-GGUF",
"filters": ["q4_k_m", "q5_k_m"]
}Response 202 Accepted (New job)
{
"id": "a1b2c3d4e5f6",
"repo": "TheBloke/Mistral-7B-Instruct-v0.2-GGUF",
"revision": "main",
"isDataset": false,
"filters": ["q4_k_m"],
"excludes": [],
"outputDir": "/home/user/.cache/huggingface/hub",
"status": "queued",
"progress": {
"totalFiles": 0,
"completedFiles": 0,
"totalBytes": 0,
"downloadedBytes": 0,
"bytesPerSecond": 0
},
"error": "",
"createdAt": "2024-01-15T10:30:00Z",
"startedAt": null,
"endedAt": null,
"files": []
}Response 200 OK (Existing job)
{
"job": { /* job object */ },
"message": "Download already in progress"
}Examples
# Basic download
curl -X POST http://localhost:8080/api/download \
-H "Content-Type: application/json" \
-d '{"repo": "TheBloke/Mistral-7B-Instruct-v0.2-GGUF"}'
# With filters
curl -X POST http://localhost:8080/api/download \
-H "Content-Type: application/json" \
-d '{
"repo": "TheBloke/Mistral-7B-Instruct-v0.2-GGUF",
"filters": ["q4_k_m", "q5_k_m"]
}'
# Dataset download
curl -X POST http://localhost:8080/api/download \
-H "Content-Type: application/json" \
-d '{"repo": "facebook/flores", "dataset": true}'
# Specific revision
curl -X POST http://localhost:8080/api/download \
-H "Content-Type: application/json" \
-d '{"repo": "CompVis/stable-diffusion-v1-4", "revision": "fp16"}'Get download plan without starting download.
Request Body
Same as /api/download.
Response 200 OK
{
"repo": "TheBloke/Mistral-7B-Instruct-v0.2-GGUF",
"revision": "main",
"files": [
{
"path": "config.json",
"size": 1024,
"lfs": false
},
{
"path": "mistral-7b.Q4_K_M.gguf",
"size": 4368438272,
"lfs": true
}
],
"totalSize": 4368439296,
"totalFiles": 2
}Example
curl -X POST http://localhost:8080/api/plan \
-H "Content-Type: application/json" \
-d '{"repo": "TheBloke/Mistral-7B-Instruct-v0.2-GGUF", "filters": ["q4_k_m"]}'List all download jobs.
Response 200 OK
{
"jobs": [
{
"id": "a1b2c3d4e5f6",
"repo": "TheBloke/Mistral-7B-Instruct-v0.2-GGUF",
"revision": "main",
"isDataset": false,
"filters": ["q4_k_m"],
"excludes": [],
"outputDir": "/home/user/.cache/huggingface/hub",
"status": "running",
"progress": {
"totalFiles": 3,
"completedFiles": 1,
"totalBytes": 4500000000,
"downloadedBytes": 1500000000,
"bytesPerSecond": 50000000
},
"error": "",
"createdAt": "2024-01-15T10:30:00Z",
"startedAt": "2024-01-15T10:30:01Z",
"endedAt": null,
"files": [
{
"path": "config.json",
"totalBytes": 1024,
"downloaded": 1024,
"status": "complete"
},
{
"path": "mistral-7b.Q4_K_M.gguf",
"totalBytes": 4368438272,
"downloaded": 1500000000,
"status": "downloading"
}
]
}
],
"count": 1
}Example
curl http://localhost:8080/api/jobsGet specific job details.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id |
string | Job ID |
Response 200 OK
{
"id": "a1b2c3d4e5f6",
"repo": "TheBloke/Mistral-7B-Instruct-v0.2-GGUF",
"status": "running",
/* ... full job object ... */
}Response 404 Not Found
{
"error": "Job not found"
}Example
curl http://localhost:8080/api/jobs/a1b2c3d4e5f6Cancel a running or queued job.
Response 200 OK
{
"success": true,
"message": "Job cancelled"
}Response 404 Not Found
{
"error": "Job not found or already completed"
}Example
curl -X DELETE http://localhost:8080/api/jobs/a1b2c3d4e5f6Pause a running job.
Response 200 OK
{
"success": true,
"message": "Job paused"
}Response 404 Not Found
{
"error": "Job not found or not running"
}Example
curl -X POST http://localhost:8080/api/jobs/a1b2c3d4e5f6/pauseResume a paused job.
Response 200 OK
{
"success": true,
"message": "Job resumed"
}Response 404 Not Found
{
"error": "Job not found or not paused"
}Note: Resumed jobs restart from queued status. Progress is reset, but already-downloaded files are automatically skipped.
Example
curl -X POST http://localhost:8080/api/jobs/a1b2c3d4e5f6/resumequeued ─────► running ─────► completed
│
├─────► failed
│
├─────► cancelled
│
└─────► paused ─────► queued ─► running ─► ...
| Status | Description |
|---|---|
queued |
Waiting to start |
running |
Download in progress |
paused |
Paused by user |
completed |
Finished successfully |
failed |
Error occurred |
cancelled |
Cancelled by user |
Get current server settings.
Response 200 OK
{
"token": "********mnop",
"cacheDir": "/home/user/.cache/huggingface/hub",
"connections": 8,
"maxActive": 3,
"multipartThreshold": "32MiB",
"verify": "size",
"retries": 4,
"endpoint": ""
}Note: Token is masked for security (shows ******** + last 4 characters).
Example
curl http://localhost:8080/api/settingsUpdate server settings.
Request Body
| Field | Type | Description |
|---|---|---|
token |
string | HuggingFace token |
connections |
integer | Connections per file |
maxActive |
integer | Max concurrent downloads |
multipartThreshold |
string | Min size for multipart |
verify |
string | Verification: none, size, sha256 |
retries |
integer | Retry attempts |
Security Restrictions
cacheDircannot be changed via APImodelsDircannot be changed via APIdatasetsDircannot be changed via API
Response 200 OK
{
"success": true,
"message": "Settings updated"
}Example
curl -X POST http://localhost:8080/api/settings \
-H "Content-Type: application/json" \
-d '{
"token": "hf_xxxxx",
"connections": 16,
"maxActive": 8
}'Analyze a HuggingFace repository.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
repo |
string | Repository ID (owner/name) |
Query Parameters
| Parameter | Type | Description |
|---|---|---|
dataset |
boolean | Force dataset type |
revision |
string | Branch/tag to analyze |
Response 200 OK (Determined type)
{
"repo": "TheBloke/Mistral-7B-Instruct-v0.2-GGUF",
"is_dataset": false,
"type": "gguf",
"type_description": "GGUF Model",
"file_count": 12,
"total_size": 4500000000,
"total_size_human": "4.2 GiB",
"branch": "main",
"refs": [
{"name": "main", "type": "branch", "commit": "abc123..."}
],
"files": [
{
"name": "config.json",
"path": "config.json",
"size": 1024,
"lfs": false
},
{
"name": "mistral-7b.Q4_K_M.gguf",
"path": "mistral-7b.Q4_K_M.gguf",
"size": 4368438272,
"lfs": true,
"sha256": "abc123..."
}
],
"gguf": {
"model_name": "Mistral-7B-Instruct-v0.2",
"quantizations": [
{
"name": "Q4_K_M",
"file": { /* FileInfo */ },
"quality": 4,
"quality_stars": "★★★★☆",
"estimated_ram": 4905066496,
"estimated_ram_human": "4.6 GiB",
"description": "Good balance of quality and size"
}
]
},
"analyzed_at": "2024-01-15T10:30:00Z"
}Response 200 OK (Needs selection)
When a repo exists as both model and dataset:
{
"needsSelection": true,
"repo": "owner/name",
"message": "This repository exists as both a model and a dataset. Please select which one you want to analyze.",
"options": ["model", "dataset"]
}Examples
# Analyze model
curl http://localhost:8080/api/analyze/TheBloke/Mistral-7B-Instruct-v0.2-GGUF
# Force dataset
curl "http://localhost:8080/api/analyze/facebook/flores?dataset=true"
# Specific revision
curl "http://localhost:8080/api/analyze/owner/repo?revision=v1.0"| Type | Description | Key Fields |
|---|---|---|
gguf |
GGUF quantized model | gguf.quantizations |
transformers |
Transformers model | transformers.architecture |
diffusers |
Diffusers pipeline | diffusers.pipeline_type |
lora |
LoRA adapter | lora.base_model |
gptq |
GPTQ quantized | gptq.bits |
awq |
AWQ quantized | awq.bits |
onnx |
ONNX model | onnx.models |
audio |
Audio model | audio.task |
vision |
Vision model | vision.task |
multimodal |
Multimodal model | multimodal.modalities |
dataset |
Dataset | dataset.formats |
List cached repositories.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
type |
string | Filter: model, dataset |
Response 200 OK
{
"repos": [
{
"repo": "TheBloke/Mistral-7B-Instruct-v0.2-GGUF",
"type": "model",
"path": "/home/user/.cache/huggingface/hub/models--TheBloke--Mistral-7B-GGUF"
},
{
"repo": "facebook/flores",
"type": "dataset",
"path": "/home/user/.cache/huggingface/hub/datasets--facebook--flores"
}
],
"count": 2,
"cacheDir": "/home/user/.cache/huggingface/hub"
}Examples
# List all
curl http://localhost:8080/api/cache
# Models only
curl "http://localhost:8080/api/cache?type=model"Get cached repository details.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
repo |
string | Repository ID (owner/name) |
Response 200 OK
{
"repo": "TheBloke/Mistral-7B-Instruct-v0.2-GGUF",
"type": "model",
"path": "/home/user/.cache/huggingface/hub/models--TheBloke--Mistral-7B-GGUF",
"snapshots": ["main", "v1.0"]
}Response 404 Not Found
{
"error": "Repository not found in cache"
}Example
curl http://localhost:8080/api/cache/TheBloke/Mistral-7B-Instruct-v0.2-GGUFReal-time updates via WebSocket connection.
ws://localhost:8080/api/ws
| Parameter | Value |
|---|---|
| Read buffer | 1024 bytes |
| Write buffer | 1024 bytes |
| Max message | 512 KB |
| Ping interval | 30 seconds |
| Read timeout | 60 seconds |
| Write timeout | 10 seconds |
All messages are JSON:
{
"type": "message_type",
"data": { /* payload */ }
}Sent immediately upon connection.
{
"type": "init",
"data": {
"jobs": [ /* all current jobs */ ],
"version": "3.0.0"
}
}Broadcast when job status changes.
{
"type": "job_update",
"data": {
"id": "a1b2c3d4e5f6",
"repo": "owner/model",
"status": "running",
"progress": {
"totalFiles": 5,
"completedFiles": 2,
"totalBytes": 5000000000,
"downloadedBytes": 2000000000,
"bytesPerSecond": 50000000
},
"files": [
{
"path": "model.bin",
"totalBytes": 5000000000,
"downloaded": 2000000000,
"status": "downloading"
}
]
}
}General event notifications.
{
"type": "event",
"data": {
"event": "download_complete",
"jobId": "a1b2c3d4e5f6"
}
}const ws = new WebSocket('ws://localhost:8080/api/ws');
ws.onopen = () => {
console.log('Connected');
};
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
switch (msg.type) {
case 'init':
console.log('Jobs:', msg.data.jobs);
break;
case 'job_update':
const job = msg.data;
const percent = (job.progress.downloadedBytes / job.progress.totalBytes * 100).toFixed(1);
console.log(`${job.repo}: ${percent}%`);
break;
}
};
ws.onerror = (err) => console.error('WebSocket error:', err);
ws.onclose = () => console.log('Disconnected');import asyncio
import websockets
import json
async def monitor():
uri = "ws://localhost:8080/api/ws"
async with websockets.connect(uri) as ws:
async for message in ws:
msg = json.loads(message)
if msg["type"] == "job_update":
job = msg["data"]
progress = job["progress"]
if progress["totalBytes"] > 0:
pct = progress["downloadedBytes"] / progress["totalBytes"] * 100
print(f"{job['repo']}: {pct:.1f}%")
asyncio.run(monitor()){
"error": "Error message",
"details": "Additional details (optional)"
}| Code | Meaning | Usage |
|---|---|---|
| 200 | OK | Successful GET, existing job |
| 202 | Accepted | New job created |
| 204 | No Content | CORS preflight |
| 400 | Bad Request | Invalid input |
| 401 | Unauthorized | Auth required/failed |
| 404 | Not Found | Resource not found |
| 500 | Server Error | Internal error |
Invalid Repository
{
"error": "Invalid repo format",
"details": "Expected owner/name"
}Job Not Found
{
"error": "Job not found"
}Analysis Failed
{
"error": "Analysis failed",
"details": "404: repository not found"
}# 1. Analyze repository
curl http://localhost:8080/api/analyze/TheBloke/Mistral-7B-Instruct-v0.2-GGUF
# 2. Start download with specific quantization
curl -X POST http://localhost:8080/api/download \
-H "Content-Type: application/json" \
-d '{"repo": "TheBloke/Mistral-7B-Instruct-v0.2-GGUF", "filters": ["q4_k_m"]}'
# 3. Monitor progress
curl http://localhost:8080/api/jobs
# 4. Get specific job
curl http://localhost:8080/api/jobs/a1b2c3d4e5f6# Start download
JOB_ID=$(curl -s -X POST http://localhost:8080/api/download \
-H "Content-Type: application/json" \
-d '{"repo": "owner/large-model"}' | jq -r '.id')
# Pause
curl -X POST "http://localhost:8080/api/jobs/$JOB_ID/pause"
# Resume later
curl -X POST "http://localhost:8080/api/jobs/$JOB_ID/resume"#!/bin/bash
# monitor.sh - Monitor download progress
JOB_ID=$1
while true; do
STATUS=$(curl -s "http://localhost:8080/api/jobs/$JOB_ID")
JOB_STATUS=$(echo "$STATUS" | jq -r '.status')
if [ "$JOB_STATUS" = "completed" ] || [ "$JOB_STATUS" = "failed" ]; then
echo "Job $JOB_STATUS"
break
fi
DOWNLOADED=$(echo "$STATUS" | jq '.progress.downloadedBytes')
TOTAL=$(echo "$STATUS" | jq '.progress.totalBytes')
if [ "$TOTAL" -gt 0 ]; then
PCT=$(echo "scale=1; $DOWNLOADED * 100 / $TOTAL" | bc)
echo "Progress: $PCT%"
fi
sleep 2
done# Get all running jobs
curl -s http://localhost:8080/api/jobs | jq '.jobs[] | select(.status == "running")'
# Get total download speed
curl -s http://localhost:8080/api/jobs | jq '[.jobs[].progress.bytesPerSecond] | add'
# List repos being downloaded
curl -s http://localhost:8080/api/jobs | jq -r '.jobs[].repo'| Header | Value |
|---|---|
Access-Control-Allow-Origin |
Configured origins or * |
Access-Control-Allow-Methods |
GET, POST, PUT, DELETE, OPTIONS |
Access-Control-Allow-Headers |
Content-Type, Authorization |
Access-Control-Max-Age |
86400 |
CORS origins are configured via server settings (code modification required for custom origins).
No rate limiting is implemented. For production deployments, consider using a reverse proxy (nginx, Caddy) with rate limiting.
- Token Protection: HF token is masked in API responses
- Directory Lock: Output directories cannot be changed via API
- Basic Auth: Optional authentication for all endpoints
- CORS: Configurable origin restrictions
- Input Validation: Repository format validation
- File Size Limits: WebSocket messages limited to 512 KB