Model Upload

Upload complete HuggingFace model directories to Xerotier.ai using archive or directory upload methods.

Overview

Xerotier.ai supports two methods for uploading complete model directories:

Method Best For Key Features
Archive Upload Standard model uploads Create a .tar.gz archive, upload in chunks, server extracts
Directory Upload Large models, incremental uploads Upload files individually, fine-grained control

Both methods use chunked uploads for reliability and support the same model formats. For basic chunked file uploads, see the Model Management API reference.

XIM deployment required. Custom uploaded models can only be deployed on XIM nodes. To use shared infrastructure, an administrator must add the model to the catalog and provision it on shared agents. See the Model Catalog documentation for details.

Archive Upload

Upload compressed archives containing HuggingFace model directories. Archives are extracted server-side, preserving the model's file structure for inference engine compatibility.

Best for: Uploading complete HuggingFace model directories. Supports .tar, .tar.gz, and .tar.bz2 formats.

Supported Formats

Format Extension Content-Type
tar .tar application/x-tar
tar.gz .tar.gz, .tgz application/gzip
tar.bz2 .tar.bz2, .tbz2 application/x-bzip2

Creating an Archive

Create a properly formatted archive from your model directory. Files must be at the archive root (not nested in subdirectories).

Basic Command
COPYFILE_DISABLE=1 tar -chzf model-name.tar.gz -C /path/to/model/directory .

Command Options

Option Description
-c Create a new archive
-h Follow symlinks (required for HuggingFace cache)
-z Compress with gzip (use -j for bzip2)
-f Specify the output filename
-C Change to directory before adding files

Important: The -h flag is required when archiving from HuggingFace cache, as it uses symlinks. Without this flag, your archive will contain broken links instead of actual model files.

HuggingFace Cache Example

bash
# Find and archive a model from HuggingFace cache COPYFILE_DISABLE=1 tar -chzf Qwen3-0.6B.tar.gz -C ~/.cache/huggingface/hub/models--Qwen--Qwen3-0.6B/snapshots/abc123/ .

Verify Archive Structure

Before uploading, verify files are at the root level:

bash
$ tar -tzf model-name.tar.gz | head -5 ./config.json ./model.safetensors ./tokenizer.json ./tokenizer_config.json ./generation_config.json

Initialize Archive Upload

POST /proj_ABC123/v1/uploads/archive

Request Body

Parameter Type Description
model_namerequired string Display name for the model
archive_sizerequired integer Total archive size in bytes
archive_formatrequired string Archive format: tar, tar.gz, or tar.bz2
descriptionoptional string Description of the model
workload_typeoptional string Workload type: chat, code, reasoning, embedding, or multilingual. Defaults to chat.
quantizationoptional string Runtime quantization method override (e.g., fp8, awq). Defaults to native.
curl
curl -X POST https://api.xerotier.ai/proj_ABC123/v1/uploads/archive \ -H "Authorization: Bearer xero_myproject_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "model_name": "Qwen3-0.6B", "archive_size": 1234567890, "archive_format": "tar.gz" }'

Response

{ "id": "00000000-1111-0000-1111-000000000000", "object": "upload", "bytes": 1234567890, "created_at": 1706123456, "filename": "Qwen3-0.6B", "purpose": "model", "status": "pending", "expires_at": 1706209856, "upload_type": "archive", "chunk_size": 104857600, "total_chunks": 12, "uploaded_chunks": 0, "progress": 0 }

Upload Archive Chunks

After initialization, upload chunks sequentially. Pass the zero-based chunk index as a query parameter:

POST /proj_ABC123/v1/uploads/{sessionId}/parts?part_number={chunkIndex}

Include an X-Chunk-Checksum header with the SHA256 hex digest of the chunk data.

Complete Archive Upload

When all chunks are uploaded, complete the upload to trigger archive extraction and model creation:

POST /proj_ABC123/v1/uploads/{sessionId}/complete

Complete Response

{ "id": "00000000-1111-0000-1111-000000000000", "object": "upload", "status": "completed", "bytes": 1234567890, "created_at": 1706123456, "filename": "Qwen3-0.6B", "upload_type": "archive", "model": { "id": "660e8400-e29b-41d4-a716-446655440001", "name": "Qwen3-0.6B", "format": "safetensors", "size_bytes": 1234567890, "status": "validating", "context_length": null, "architecture": null, "quantization": "native" } }

Archive Upload Example

Python
import os import hashlib import requests API_KEY = "xero_myproject_your_api_key" BASE_URL = "https://api.xerotier.ai/proj_ABC123/v1" def upload_archive(archive_path: str, model_name: str): archive_size = os.path.getsize(archive_path) # Initialize init = requests.post(f"{BASE_URL}/uploads/archive", headers={"Authorization": f"Bearer {API_KEY}"}, json={"model_name": model_name, "archive_size": archive_size, "archive_format": "tar.gz"} ).json() session_id = init["id"] chunk_size = init["chunk_size"] # Upload chunks with open(archive_path, "rb") as f: idx = 0 while chunk := f.read(chunk_size): checksum = hashlib.sha256(chunk).hexdigest() requests.post(f"{BASE_URL}/uploads/{session_id}/parts?part_number={idx}", headers={"Authorization": f"Bearer {API_KEY}", "X-Chunk-Checksum": checksum}, data=chunk) idx += 1 # Complete return requests.post(f"{BASE_URL}/uploads/{session_id}/complete", headers={"Authorization": f"Bearer {API_KEY}"}).json() upload_archive("./qwen3-0.6b.tar.gz", "Qwen3-0.6B")

Node.js Archive Upload Example

Node.js
import { readFile, stat } from "node:fs/promises"; import { createHash } from "node:crypto"; const API_KEY = "xero_myproject_your_api_key"; const BASE_URL = "https://api.xerotier.ai/proj_ABC123/v1"; async function uploadArchive(archivePath, modelName) { const fileInfo = await stat(archivePath); const archiveSize = fileInfo.size; // Initialize const initResponse = await fetch(`${BASE_URL}/uploads/archive`, { method: "POST", headers: { "Authorization": `Bearer ${API_KEY}`, "Content-Type": "application/json" }, body: JSON.stringify({ model_name: modelName, archive_size: archiveSize, archive_format: "tar.gz" }) }); const init = await initResponse.json(); const sessionId = init.id; const chunkSize = init.chunk_size; // Read and upload chunks const fileData = await readFile(archivePath); for (let idx = 0; idx * chunkSize < fileData.length; idx++) { const chunk = fileData.subarray(idx * chunkSize, (idx + 1) * chunkSize); const checksum = createHash("sha256").update(chunk).digest("hex"); await fetch( `${BASE_URL}/uploads/${sessionId}/parts?part_number=${idx}`, { method: "POST", headers: { "Authorization": `Bearer ${API_KEY}`, "X-Chunk-Checksum": checksum }, body: chunk } ); } // Complete const completeResponse = await fetch( `${BASE_URL}/uploads/${sessionId}/complete`, { method: "POST", headers: { "Authorization": `Bearer ${API_KEY}` } } ); return completeResponse.json(); } await uploadArchive("./qwen3-0.6b.tar.gz", "Qwen3-0.6B");

Directory Upload

Upload individual files that mirror a local model directory structure. Useful when creating an archive is impractical or for incremental uploads.

Best for: Large models where archive creation is slow, or when you want fine-grained control over individual file uploads.

Initialize Directory Upload

POST /proj_ABC123/v1/uploads/directory

Provide a manifest of all files to upload:

Request Body

Parameter Type Description
model_namerequired string Display name for the model
filesrequired array List of files to upload. Each entry must include relative_path (string) and size (integer bytes).
descriptionoptional string Description of the model
workload_typeoptional string Workload type: chat, code, reasoning, embedding, or multilingual. Defaults to chat.
quantizationoptional string Runtime quantization method override (e.g., fp8, awq). Defaults to native.
curl
curl -X POST https://api.xerotier.ai/proj_ABC123/v1/uploads/directory \ -H "Authorization: Bearer xero_myproject_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "model_name": "Qwen3-0.6B", "files": [ {"relative_path": "config.json", "size": 1234}, {"relative_path": "model.safetensors", "size": 1234000000}, {"relative_path": "tokenizer.json", "size": 5678} ] }'

Response

{ "id": "00000000-1111-0000-1111-000000000000", "object": "upload", "bytes": 1234001234, "created_at": 1706123456, "filename": "Qwen3-0.6B", "purpose": "model", "status": "pending", "expires_at": 1706209856, "upload_type": "directory", "chunk_size": 104857600, "uploaded_chunks": 0, "progress": 0, "files": [ { "relative_path": "config.json", "upload_path": "v1/uploads/00000000.../files/config.json", "size": 1234, "requires_chunking": false, "status": "pending" }, { "relative_path": "model.safetensors", "upload_path": "v1/uploads/00000000.../files/model.safetensors", "size": 1234000000, "requires_chunking": true, "total_chunks": 12, "chunk_url": "v1/uploads/00000000.../file-chunks", "status": "pending" } ] }

Upload Individual File

POST /proj_ABC123/v1/uploads/{sessionId}/files/{relativePath}

Headers

Header Description
X-File-Checksum SHA256 checksum of the file content (hex-encoded)
curl
CHECKSUM=$(sha256sum config.json | cut -d' ' -f1) curl -X POST "https://api.xerotier.ai/proj_ABC123/v1/uploads/$SESSION_ID/files/config.json" \ -H "Authorization: Bearer xero_myproject_your_api_key" \ -H "X-File-Checksum: $CHECKSUM" \ --data-binary @config.json

Response

{ "relative_path": "config.json", "size": 1234, "checksum": "a1b2c3d4e5f6...", "uploaded_file_count": 1, "expected_file_count": 7, "progress": 14.28 }

Directory Upload Example

Python
import os import hashlib import requests from pathlib import Path API_KEY = "xero_myproject_your_api_key" BASE_URL = "https://api.xerotier.ai/proj_ABC123/v1" def upload_directory(model_dir: str, model_name: str): model_path = Path(model_dir) # Build manifest files = [{"relative_path": str(f.relative_to(model_path)), "size": f.stat().st_size} for f in model_path.rglob("*") if f.is_file()] # Initialize init = requests.post(f"{BASE_URL}/uploads/directory", headers={"Authorization": f"Bearer {API_KEY}"}, json={"model_name": model_name, "files": files} ).json() session_id = init["id"] # Upload each file for file_info in files: file_path = model_path / file_info["relative_path"] with open(file_path, "rb") as f: data = f.read() checksum = hashlib.sha256(data).hexdigest() requests.post(f"{BASE_URL}/uploads/{session_id}/files/{file_info['relative_path']}", headers={"Authorization": f"Bearer {API_KEY}", "X-File-Checksum": checksum}, data=data) # Complete return requests.post(f"{BASE_URL}/uploads/{session_id}/complete", headers={"Authorization": f"Bearer {API_KEY}"}).json() upload_directory("./models--Qwen--Qwen3-0.6B", "Qwen3-0.6B")

Node.js Directory Upload Example

Node.js
import { readFile, stat, readdir } from "node:fs/promises"; import { createHash } from "node:crypto"; import { join, relative } from "node:path"; const API_KEY = "xero_myproject_your_api_key"; const BASE_URL = "https://api.xerotier.ai/proj_ABC123/v1"; async function getFiles(dir, base) { const entries = await readdir(dir, { withFileTypes: true }); const files = []; for (const entry of entries) { const fullPath = join(dir, entry.name); if (entry.isFile()) { const info = await stat(fullPath); files.push({ relative_path: relative(base, fullPath), size: info.size }); } else if (entry.isDirectory()) { files.push(...await getFiles(fullPath, base)); } } return files; } async function uploadDirectory(modelDir, modelName) { const files = await getFiles(modelDir, modelDir); // Initialize const initResponse = await fetch(`${BASE_URL}/uploads/directory`, { method: "POST", headers: { "Authorization": `Bearer ${API_KEY}`, "Content-Type": "application/json" }, body: JSON.stringify({ model_name: modelName, files }) }); const init = await initResponse.json(); const sessionId = init.id; // Upload each file for (const fileInfo of files) { const data = await readFile(join(modelDir, fileInfo.relative_path)); const checksum = createHash("sha256").update(data).digest("hex"); await fetch( `${BASE_URL}/uploads/${sessionId}/files/${fileInfo.relative_path}`, { method: "POST", headers: { "Authorization": `Bearer ${API_KEY}`, "X-File-Checksum": checksum }, body: data } ); } // Complete const completeResponse = await fetch( `${BASE_URL}/uploads/${sessionId}/complete`, { method: "POST", headers: { "Authorization": `Bearer ${API_KEY}` } } ); return completeResponse.json(); } await uploadDirectory("./models--Qwen--Qwen3-0.6B", "Qwen3-0.6B");

Complete Directory Upload

After all files are uploaded, call the complete endpoint to finalize the model record and trigger metadata extraction:

POST /proj_ABC123/v1/uploads/{sessionId}/complete

Returns a RouterUploadCompleteResponse with the same shape as the archive complete response above.

Resuming Interrupted Uploads

If an upload is interrupted, use the resume endpoint to find out which chunks or files still need to be uploaded:

POST /proj_ABC123/v1/uploads/{sessionId}/resume

curl
curl -X POST https://api.xerotier.ai/proj_ABC123/v1/uploads/00000000-1111-0000-1111-000000000000/resume \ -H "Authorization: Bearer xero_myproject_your_api_key"

Response

{ "id": "00000000-1111-0000-1111-000000000000", "next_chunk_index": 5, "uploaded_chunks": 5, "missing_chunks": [] }

Resume by uploading starting from next_chunk_index, or re-uploading any missing_chunks. Sessions expire after 24 hours of inactivity.

Required Model Files

For inference engine compatibility, models must include:

File Required Description
config.json Yes Model configuration
*.safetensors or *.bin or *.exl2 Yes Model weights (at least one)
tokenizer.json No Tokenizer configuration
tokenizer_config.json No Tokenizer settings