API Reference
Submit Python files for protection and retrieve compiled artifacts programmatically. All API endpoints are under /api/v1/ and require an API key.
Overview
The PyVMProtect API lets you integrate Python protection into your own build pipeline. The typical flow is:
- Submit a
.pyor.zipfile → receive ajob_id - Poll the job status until
statusiscompleteorerror - (Optional) Confirm Cython hot-paths if status reaches
hot_path_pending - Download the protected artifact
Authentication
All /api/v1/ endpoints require an API key in the Authorization header:
Authorization: Bearer pvmp_your_api_key_here
To generate your API key, go to My Account and click Generate Key in the API Key section. Regenerating a key immediately invalidates the previous one.
Endpoints
/api/v1/job_submit.php
Upload a Python file or project zip and start a build job.
Request
Content-Type: multipart/form-data
| Field | Type | Description |
|---|---|---|
| file | file | Required. .py or .zip. Web beta builds currently allow up to 100 MB source uploads; API-key builds allow up to 200 MB. |
| module_name | string | Output module name. Defaults to main. Alphanumeric and underscores only. |
| options | JSON string | Optional build options object (see options below) |
| hot_paths | JSON array | Optional list of function names to pre-select for Cython protection |
Response 200
{
"job_id": "a3f9bc12e4561234a3f9bc12e4561234",
"status": "queued",
"module_name": "my_module",
"is_zip": false
}
/api/v1/job_status.php?job_id=<id>
Poll the current status of a build job. Call this every 1–3 seconds until status is complete or error.
Response 200
{
"job_id": "a3f9bc12e4561234...",
"status": "building",
"module_name": "my_module",
"is_zip": false,
"progress_pct": 45,
"progress_label": "Compiling extensions",
"created_at": 1713700000,
"updated_at": 1713700042,
// Present when status = "hot_path_pending":
"functions": ["encrypt_payload", "verify_license", "decode_config"],
// Present when status = "complete":
"download_token": "abc123..."
}
/api/v1/job_hotpath.php
Confirm which functions to Cython-compile when a job is in hot_path_pending state. Pass an empty array to skip Cython optimization and continue with standard protection.
Request body (JSON)
{
"job_id": "a3f9bc12e4561234...",
"hot_paths": ["encrypt_payload", "verify_license"]
}
Response 200
{ "ok": true, "job_id": "...", "hot_paths": ["encrypt_payload", "verify_license"] }
/api/v1/job_download.php?job_id=<id>
Download the protected artifact as a .zip file. Only available when status = complete. The artifact is deleted after the first successful download (one-time link, 1-hour TTL).
Response
Binary application/zip stream with Content-Disposition: attachment. On error, returns JSON.
Full Workflow
The complete lifecycle of a build job:
POST /api/v1/job_submit.php → { job_id }
↓
GET /api/v1/job_status.php?job_id= → status: queued
↓ (poll every 2s)
GET /api/v1/job_status.php?job_id= → status: building, progress_pct: 45
↓
GET /api/v1/job_status.php?job_id= → status: hot_path_pending, functions: [...]
↓ (optional — only if Cython is enabled)
POST /api/v1/job_hotpath.php → { ok: true }
↓
GET /api/v1/job_status.php?job_id= → status: complete, download_token: "..."
↓
GET /api/v1/job_download.php?job_id= → .zip download
Code Examples
curl
Note: The API blocks the default curl/* user-agent. Add -H "User-Agent: MyApp/1.0" (any non-curl UA) to all requests.
# 1. Submit a file
curl -X POST https://pyvmprotect.com/api/v1/job_submit.php \
-H "Authorization: Bearer pvmp_your_key_here" \
-H "User-Agent: MyApp/1.0" \
-F "file=@my_script.py" \
-F "module_name=my_module"
# 2. Poll status
curl https://pyvmprotect.com/api/v1/job_status.php?job_id=JOB_ID \
-H "Authorization: Bearer pvmp_your_key_here" \
-H "User-Agent: MyApp/1.0"
# 3. Download when complete
curl -O https://pyvmprotect.com/api/v1/job_download.php?job_id=JOB_ID \
-H "Authorization: Bearer pvmp_your_key_here" \
-H "User-Agent: MyApp/1.0"
Python
import time
import requests
API_KEY = "pvmp_your_key_here"
BASE = "https://pyvmprotect.com/api/v1"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
# 1. Submit
with open("my_script.py", "rb") as f:
resp = requests.post(
f"{BASE}/job_submit.php",
headers=HEADERS,
files={"file": ("my_script.py", f, "text/x-python")},
data={"module_name": "my_module"},
)
resp.raise_for_status()
job_id = resp.json()["job_id"]
print(f"Job submitted: {job_id}")
# 2. Poll until done
while True:
status = requests.get(
f"{BASE}/job_status.php",
headers=HEADERS,
params={"job_id": job_id},
).json()
s = status["status"]
print(f" {s} ({status.get('progress_pct', 0)}%)")
if s == "hot_path_pending":
# Confirm all suggested functions (or pass a subset)
requests.post(
f"{BASE}/job_hotpath.php",
headers=HEADERS,
json={"job_id": job_id, "hot_paths": status["functions"]},
).raise_for_status()
elif s == "complete":
break
elif s == "error":
raise RuntimeError(f"Build failed: {status.get('error')}")
time.sleep(2)
# 3. Download
resp = requests.get(
f"{BASE}/job_download.php",
headers=HEADERS,
params={"job_id": job_id},
)
resp.raise_for_status()
with open("my_module_protected.zip", "wb") as f:
f.write(resp.content)
print("Downloaded: my_module_protected.zip")
Status Values
| Status | Meaning |
|---|---|
| queued | Job is waiting in the build queue |
| building | Daemon is actively compiling the submission |
| hot_path_pending | Awaiting your hot-path selection — call job_hotpath.php to continue |
| complete | Build succeeded — artifact available for download |
| error | Build failed — see the error field for details |
Rate Limits
| Account type | Endpoint | Limit |
|---|---|---|
| Free | Web Forge builds | 3 submissions per hour, 1 active job per user |
| Beta / Premium | Web Forge builds | 10 submissions per hour, 2 active jobs per user |
| Beta / Premium | API-key builds | No hourly submit cap, 2 active jobs per user |
When a rate limit or active-job limit is exceeded the API returns HTTP 429 with an error message. Retry after an existing build finishes or after the hourly window resets. Free accounts cannot use the API key endpoint — a Beta or Premium plan is required.
Errors
All errors return JSON with an error field:
{ "error": "Invalid API key" }
| HTTP Code | Meaning |
|---|---|
400 | Bad request — missing or invalid parameter |
401 | Missing or invalid API key |
404 | Job not found (or belongs to another account) |
405 | Wrong HTTP method |
409 | Job is in the wrong state for this operation |
410 | Artifact already downloaded or link expired |
413 | File too large |
429 | Rate limit exceeded |
500 | Server error — contact support |