API Reference
Base URL: http://<your-server-ip-or-domain>:8080
Authentication
All business endpoints require this header:
| Header | Value | Required |
|---|---|---|
X-API-Key | Your API key | ✅ |
Security note
Never hardcode the API key in frontend code. Use it in backend services or CI pipelines.
Async Task Flow
POST compress → returns task_id
GET tasks/{id} → pending / processing / completed / failed
GET download → download the result file| status | Meaning | Action |
|---|---|---|
pending | Queued | Keep polling |
processing | In progress | Keep polling |
completed | Done | Call download |
failed | Failed | Check the error field |
1. System Status
GET /api/v1/health
No authentication required.
Response 200
{ "status": "ok" }GET /api/v1/capabilities
Query the capabilities supported by this service.
Response 200
{
"engines": ["draco", "meshopt"],
"formats": ["glb", "gltf", "obj", "fbx", "stl", "dae", "ply"],
"features": ["compress", "convert", "repair", "diagnose", "texture_optimize", "texture_ktx2"]
}2. Model Compression
POST /api/v1/compress
Submit a compression task.
Headers
| Header | Value |
|---|---|
| Content-Type | multipart/form-data |
| X-API-Key | Your key |
Body (form-data)
| Field | Type | Required | Description |
|---|---|---|---|
file | File | ✅ | Model file (GLB/glTF/OBJ/FBX, etc.) |
engine | string | ✅ | draco or meshopt |
level | integer | ❌ | Compression level 1–10, default 7. Higher = better compression |
position_quantization_bits | integer | ❌ | Position precision (Draco only), default 14, range 8–24 |
normal_quantization_bits | integer | ❌ | Normal precision (Draco only), default 10 |
texcoord_quantization_bits | integer | ❌ | UV precision (Draco only), default 12 |
color_quantization_bits | integer | ❌ | Color precision (Draco only), default 8 |
generic_quantization_bits | integer | ❌ | Generic attribute precision (Draco only), default 12 |
meshopt_compression_level | string | ❌ | Meshopt level: high / medium / low, default high |
Response 200
{
"task_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "pending",
"message": "Task submitted, queued"
}3. Format Conversion
POST /api/v1/convert
Convert a model from one format to another.
Headers
| Header | Value |
|---|---|
| Content-Type | multipart/form-data |
| X-API-Key | Your key |
Body (form-data)
| Field | Type | Required | Description |
|---|---|---|---|
file | File | ✅ | Model file |
target_format | string | ✅ | glb / gltf / obj / fbx / stl / dae / ply |
When the target is
gltf, the output is a.zipbundle (containing the .gltf + .bin + textures).
Response 200
{
"task_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"status": "pending"
}4. Model Repair
POST /api/v1/repair
Automatically removes empty nodes, fixes normals, merges duplicate vertices, drops degenerate faces, etc.
Headers
| Header | Value |
|---|---|
| Content-Type | multipart/form-data |
| X-API-Key | Your key |
Body (form-data)
| Field | Type | Required | Description |
|---|---|---|---|
file | File | ✅ | Model file |
Response 200
{
"task_id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
"status": "pending"
}5. Model Diagnosis
POST /api/v1/diagnose
Runs a full health check on the model and produces a detailed report (does not modify the original file).
Headers
| Header | Value |
|---|---|
| Content-Type | multipart/form-data |
| X-API-Key | Your key |
Body (form-data)
| Field | Type | Required | Description |
|---|---|---|---|
file | File | ✅ | Model file |
Response 200 (after the task completes)
The diagnosis result is embedded in the result field of the task query response:
{
"result": {
"format": "glb",
"version": "2.0",
"file_size_bytes": 15728640,
"meshes": [{
"name": "Mesh_0",
"vertices": 24568,
"triangles": 48192,
"has_normals": true,
"has_texcoords": true,
"has_colors": false,
"has_tangents": true,
"skinned": false,
"morph_targets": 0,
"issues": [
{ "type": "degenerate_faces", "count": 12, "severity": "low" },
{ "type": "duplicate_vertices", "count": 234, "severity": "medium" }
]
}],
"textures": [
{ "name": "baseColorTexture", "format": "jpeg", "width": 2048, "height": 2048, "size_kb": 512 }
],
"animations": [],
"total_issues": 246,
"recommendations": ["Consider running repair to fix degenerate faces and duplicate vertices"]
}
}6. Texture Optimization
POST /api/v1/textures/optimize
Compress model textures or standalone images to JPEG/WebP/PNG.
Headers
| Header | Value |
|---|---|
| Content-Type | multipart/form-data |
| X-API-Key | Your key |
Body (form-data)
| Field | Type | Required | Description |
|---|---|---|---|
file | File | ✅ | Model file or standalone image |
format | string | ✅ | jpeg / webp / png |
quality | integer | ❌ | Quality 1–100, default 82 (JPEG/WebP); PNG compression level 1–9, default 6 |
Response 200
{
"task_id": "d4e5f6a7-b8c9-0123-defa-234567890123",
"status": "pending"
}POST /api/v1/textures/ktx2
KTX2 (Basis Universal) encoding — loads directly on the GPU, common for Web3D.
Headers
| Header | Value |
|---|---|
| Content-Type | multipart/form-data |
| X-API-Key | Your key |
Body (form-data)
| Field | Type | Required | Description |
|---|---|---|---|
file | File | ✅ | Model file or image (PNG/JPEG) |
uastc | boolean | ❌ | UASTC high-quality mode, default false (ETC1S) |
quality | integer | ❌ | ETC1S: 1–255, default 128; UASTC: 0–4, default 2 |
Response 200
{
"task_id": "e5f6a7b8-c9d0-1234-efab-345678901234",
"status": "pending"
}ETC1S vs UASTC
| ETC1S (default) | UASTC | |
|---|---|---|
| Compression ratio | High (~1/10) | Medium (~1/4) |
| Visual quality | Slight blur on complex textures | Near-original quality |
| Best for | Mobile / size-sensitive | Desktop / high fidelity |
7. Task Management
GET /api/v1/tasks/{task_id}
Query task status and progress by task_id.
Path parameters
| Parameter | Type | Description |
|---|---|---|
task_id | string | Unique task identifier |
Response 200 — in progress
{
"task_id": "a1b2c3d4-...",
"status": "processing",
"progress": 65,
"created_at": "2026-08-03T10:30:00Z",
"started_at": "2026-08-03T10:30:01Z",
"type": "compress",
"input_filename": "model.glb",
"input_size_bytes": 15728640
}Response 200 — completed
{
"task_id": "a1b2c3d4-...",
"status": "completed",
"progress": 100,
"created_at": "2026-08-03T10:30:00Z",
"completed_at": "2026-08-03T10:30:05Z",
"type": "compress",
"input_filename": "model.glb",
"input_size_bytes": 15728640,
"output": {
"filename": "model_optimized.glb",
"size_bytes": 3145728,
"compression_ratio": 0.20,
"savings_percent": 80.0
}
}Response 200 — failed
{
"task_id": "a1b2c3d4-...",
"status": "failed",
"error": "draco_transcoder execution failed: exit code 1",
"created_at": "2026-08-03T10:30:00Z",
"failed_at": "2026-08-03T10:30:02Z"
}Response field reference
| Field | Type | Description |
|---|---|---|
task_id | string | Unique task identifier |
status | string | pending / processing / completed / failed |
progress | integer | Progress 0–100 |
type | string | compress / convert / repair / diagnose / texture_optimize / texture_ktx2 |
input_filename | string | Original file name |
input_size_bytes | integer | Original size (bytes) |
output.filename | string | Output file name (when completed) |
output.size_bytes | integer | Output size in bytes (when completed) |
output.compression_ratio | float | Compression ratio (compress/texture tasks) |
output.savings_percent | float | Size reduction percentage (compress/texture tasks) |
error | string | Error reason (when failed) |
created_at | string | Creation time, ISO 8601 |
started_at | string | Start time |
completed_at / failed_at | string | Finish time |
GET /api/v1/tasks/{task_id}/download
Download the processed result file.
Path parameters
| Parameter | Type | Description |
|---|---|---|
task_id | string | ID of a completed task |
Response 200
Returns the file as a binary stream (Content-Type: application/octet-stream).
8. Batch Processing
POST /api/v1/batch/compress
Submit multiple files for compression in one request; each file becomes an independent sub-task.
Headers
| Header | Value |
|---|---|
| Content-Type | multipart/form-data |
| X-API-Key | Your key |
Body (form-data)
| Field | Type | Required | Description |
|---|---|---|---|
files | File[] | ✅ | Multiple model files |
engine | string | ✅ | draco or meshopt |
level | integer | ❌ | Compression level 1–10, default 7 |
Response 200
{
"batch_id": "batch-abc123",
"total": 3,
"tasks": [
{ "task_id": "t1-...", "filename": "model1.glb", "status": "pending" },
{ "task_id": "t2-...", "filename": "model2.fbx", "status": "pending" },
{ "task_id": "t3-...", "filename": "scene.gltf", "status": "pending" }
]
}GET /api/v1/batch/{batch_id}
Query the overall progress of a batch.
Path parameters
| Parameter | Type | Description |
|---|---|---|
batch_id | string | Unique batch identifier |
Response 200
{
"batch_id": "batch-abc123",
"status": "processing",
"total": 3, "completed": 1, "failed": 0,
"tasks": [
{ "task_id": "t1-...", "filename": "model1.glb", "status": "completed" },
{ "task_id": "t2-...", "filename": "model2.fbx", "status": "processing" },
{ "task_id": "t3-...", "filename": "scene.gltf", "status": "pending" }
]
}Sub-tasks can be operated individually via task query and result download.
9. Licensing
POST /api/v1/license/verify
Verify whether a License Key is valid.
Body (JSON)
{ "license_key": "xxxx-xxxx-xxxx-xxxx" }Response 200
{
"valid": true,
"tier": "pro",
"expires_at": "2027-08-03T00:00:00Z",
"limits": { "max_file_size_mb": 500, "max_batch_count": 100 }
}GET /api/v1/license/info
Query the current license status.
Response 200
{
"mode": "licensed",
"tier": "enterprise",
"license_key": "****-****-****-1234",
"expires_at": "2027-12-31T00:00:00Z",
"trial": { "enabled": true, "used_files": 12, "max_files": 50 },
"limits": {
"max_file_size_mb": 500,
"max_batch_count": -1,
"max_texture_size_mb": 500
}
}
-1means unlimited.
10. Monitoring
GET /api/v1/metrics
Prometheus-format metrics for Grafana integration.
| Metric | Meaning |
|---|---|
zipoly_tasks_total | Total tasks (by type/status) |
zipoly_task_duration_seconds | Task duration distribution |
zipoly_files_processed_total | Total files processed |
zipoly_compression_ratio | Compression ratio distribution |
zipoly_active_tasks_gauge | Current concurrent task count |
Error Codes
| HTTP | Meaning | Typical scenario |
|---|---|---|
| 400 | Bad request | Unsupported format, missing required field |
| 401 | Unauthorized | Missing/wrong X-API-Key |
| 404 | Not found | Invalid task_id / batch_id |
| 413 | Payload too large | Exceeds the plan limit |
| 429 | Rate limited | Exceeded requests per minute |
| 500 | Internal error | Engine execution failure, insufficient disk |
Error response format
{ "error": "error description" }| Scenario | Example |
|---|---|
| Unsupported format | { "error": "unsupported format: .max" } |
| Missing required field | { "error": "missing required field: engine" } |
| Unauthorized | { "error": "unauthorized: missing or invalid X-API-Key header" } |
| File too large | { "error": "file size 512MB exceeds limit of 500MB for current license tier" } |
| Rate limited | { "error": "rate limit exceeded: max 60 requests per minute per API key" } |
| Internal error | { "error": "internal error: draco_transcoder execution failed" } |
Rate Limiting
Each key is limited to 60 requests per minute. Exceeding it returns 429 with these response headers:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1691123456Adjustable via ZIPOLY__SERVER__RATE_LIMIT_PER_MINUTE — see Configuration.