HFDownloader REST API Reference

January 30, 2026 · View on GitHub

Complete REST API documentation for hfdownloader serve.


Table of Contents


Overview

Start the server:

hfdownloader serve --port 8080

The server provides:

  • REST API for download management
  • WebSocket for real-time progress updates
  • Web UI at the root URL

Content Types

  • Request bodies: application/json
  • Response bodies: application/json
  • WebSocket messages: JSON

Authentication

Basic Authentication (Optional)

Enable with server flags:

hfdownloader serve --auth-user admin --auth-pass secret123

All requests require the Authorization header:

Authorization: Basic YWRtaW46c2VjcmV0MTIz

HuggingFace Token

For private/gated models, provide via:

  1. Server flag: hfdownloader serve -t hf_xxxxx
  2. Settings API: POST /api/settings

Base URL

http://localhost:8080/api

Endpoints

Health Check

Check server status.

GET /api/health

Response 200 OK

{
  "status": "ok",
  "version": "3.0.0",
  "time": "2024-01-15T10:30:00Z"
}

Example

curl http://localhost:8080/api/health

Downloads

POST /api/download

Start a new download job.

Request Body

FieldTypeRequiredDefaultDescription
repostringYesRepository ID (owner/name)
revisionstringNomainBranch, tag, or commit
datasetbooleanNofalseTreat as dataset
filtersstring[]No[]File filter patterns
excludesstring[]No[]Exclude patterns
appendFilterSubdirbooleanNofalseCreate filter subdirs
dryRunbooleanNofalsePlan 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"}'

POST /api/plan

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"]}'

Jobs

GET /api/jobs

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/jobs

GET /api/jobs/{id}

Get specific job details.

Path Parameters

ParameterTypeDescription
idstringJob 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/a1b2c3d4e5f6

DELETE /api/jobs/{id}

Cancel 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/a1b2c3d4e5f6

POST /api/jobs/{id}/pause

Pause 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/pause

POST /api/jobs/{id}/resume

Resume 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/resume

Job Status Lifecycle

queued ─────► running ─────► completed

                ├─────► failed

                ├─────► cancelled

                └─────► paused ─────► queued ─► running ─► ...
StatusDescription
queuedWaiting to start
runningDownload in progress
pausedPaused by user
completedFinished successfully
failedError occurred
cancelledCancelled by user

Settings

GET /api/settings

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/settings

POST /api/settings

Update server settings.

Request Body

FieldTypeDescription
tokenstringHuggingFace token
connectionsintegerConnections per file
maxActiveintegerMax concurrent downloads
multipartThresholdstringMin size for multipart
verifystringVerification: none, size, sha256
retriesintegerRetry attempts

Security Restrictions

  • cacheDir cannot be changed via API
  • modelsDir cannot be changed via API
  • datasetsDir cannot 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
  }'

Analyzer

GET /api/analyze/{repo}

Analyze a HuggingFace repository.

Path Parameters

ParameterTypeDescription
repostringRepository ID (owner/name)

Query Parameters

ParameterTypeDescription
datasetbooleanForce dataset type
revisionstringBranch/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"

Detected Model Types

TypeDescriptionKey Fields
ggufGGUF quantized modelgguf.quantizations
transformersTransformers modeltransformers.architecture
diffusersDiffusers pipelinediffusers.pipeline_type
loraLoRA adapterlora.base_model
gptqGPTQ quantizedgptq.bits
awqAWQ quantizedawq.bits
onnxONNX modelonnx.models
audioAudio modelaudio.task
visionVision modelvision.task
multimodalMultimodal modelmultimodal.modalities
datasetDatasetdataset.formats

Cache

GET /api/cache

List cached repositories.

Query Parameters

ParameterTypeDescription
typestringFilter: 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 /api/cache/{repo}

Get cached repository details.

Path Parameters

ParameterTypeDescription
repostringRepository 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-GGUF

WebSocket API

Real-time updates via WebSocket connection.

Connection

ws://localhost:8080/api/ws

Connection Parameters

ParameterValue
Read buffer1024 bytes
Write buffer1024 bytes
Max message512 KB
Ping interval30 seconds
Read timeout60 seconds
Write timeout10 seconds

Message Format

All messages are JSON:

{
  "type": "message_type",
  "data": { /* payload */ }
}

Message Types

init

Sent immediately upon connection.

{
  "type": "init",
  "data": {
    "jobs": [ /* all current jobs */ ],
    "version": "3.0.0"
  }
}

job_update

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"
      }
    ]
  }
}

event

General event notifications.

{
  "type": "event",
  "data": {
    "event": "download_complete",
    "jobId": "a1b2c3d4e5f6"
  }
}

JavaScript Example

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');

Python Example

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 Handling

Error Response Format

{
  "error": "Error message",
  "details": "Additional details (optional)"
}

HTTP Status Codes

CodeMeaningUsage
200OKSuccessful GET, existing job
202AcceptedNew job created
204No ContentCORS preflight
400Bad RequestInvalid input
401UnauthorizedAuth required/failed
404Not FoundResource not found
500Server ErrorInternal error

Common Errors

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"
}

Examples

Complete Download Workflow

# 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

Pause/Resume Workflow

# 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"

Real-time Monitoring Script

#!/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

Integration with jq

# 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'

CORS

Headers

HeaderValue
Access-Control-Allow-OriginConfigured origins or *
Access-Control-Allow-MethodsGET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-HeadersContent-Type, Authorization
Access-Control-Max-Age86400

Configuration

CORS origins are configured via server settings (code modification required for custom origins).


Rate Limiting

No rate limiting is implemented. For production deployments, consider using a reverse proxy (nginx, Caddy) with rate limiting.


Security Considerations

  1. Token Protection: HF token is masked in API responses
  2. Directory Lock: Output directories cannot be changed via API
  3. Basic Auth: Optional authentication for all endpoints
  4. CORS: Configurable origin restrictions
  5. Input Validation: Repository format validation
  6. File Size Limits: WebSocket messages limited to 512 KB

See Also