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:

  1. Submit a .py or .zip file → receive a job_id
  2. Poll the job status until status is complete or error
  3. (Optional) Confirm Cython hot-paths if status reaches hot_path_pending
  4. Download the protected artifact
An account is required to use the API. Create a free account to get started.

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.

Keep your API key secret. Do not commit it to version control or expose it in client-side code.

Endpoints

POST /api/v1/job_submit.php

Upload a Python file or project zip and start a build job.

Request

Content-Type: multipart/form-data

FieldTypeDescription
filefileRequired. .py or .zip. Web beta builds currently allow up to 100 MB source uploads; API-key builds allow up to 200 MB.
module_namestringOutput module name. Defaults to main. Alphanumeric and underscores only.
optionsJSON stringOptional build options object (see options below)
hot_pathsJSON arrayOptional list of function names to pre-select for Cython protection

Response 200

{
  "job_id":      "a3f9bc12e4561234a3f9bc12e4561234",
  "status":      "queued",
  "module_name": "my_module",
  "is_zip":      false
}
GET /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..."
}
POST /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"] }
GET /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

StatusMeaning
queuedJob is waiting in the build queue
buildingDaemon is actively compiling the submission
hot_path_pendingAwaiting your hot-path selection — call job_hotpath.php to continue
completeBuild succeeded — artifact available for download
errorBuild failed — see the error field for details

Rate Limits

Account typeEndpointLimit
FreeWeb Forge builds3 submissions per hour, 1 active job per user
Beta / PremiumWeb Forge builds10 submissions per hour, 2 active jobs per user
Beta / PremiumAPI-key buildsNo 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 CodeMeaning
400Bad request — missing or invalid parameter
401Missing or invalid API key
404Job not found (or belongs to another account)
405Wrong HTTP method
409Job is in the wrong state for this operation
410Artifact already downloaded or link expired
413File too large
429Rate limit exceeded
500Server error — contact support
Join our Discord