Quickstart

Your first analysis in 5 minutes

Upload an STL, read its geometry, and download a thickness heatmap. Every example is copy-and-paste, in cURL and Python.

1. Get your API keys

Create a free account, then press Generate API keys on the API keys page. Copy the secret key right away: Propermesh only keeps a hash of it, so it's shown once. Every request sends the two keys as headers.

Shell
export PROPERMESH_ACCESS_KEY="your access key"
export PROPERMESH_SECRET_KEY="your secret key"

The Python examples share these helpers. Put them at the top of your script:

Python
# pip install requests
import os
import time
import requests

BASE = "https://propermesh.com/api/v0.1"
HEADERS = {
    "x-access-key": os.environ["PROPERMESH_ACCESS_KEY"],
    "x-secret-access-key": os.environ["PROPERMESH_SECRET_KEY"],
}


def post(path, **kwargs):
    """Uploads a part or starts a job, and returns the response body"""
    response = requests.post(f"{BASE}/{path}", headers=HEADERS, **kwargs)
    response.raise_for_status()
    return response.json()


def wait_for(path):
    """Polls a job until it finishes. Returns it, or raises if it failed or was cancelled"""
    while True:
        response = requests.get(f"{BASE}/{path}", headers=HEADERS)
        response.raise_for_status()
        job = response.json()
        if job["status"] == "complete":
            return job
        if job["status"] != "in_progress":
            raise RuntimeError(f"{path} {job['status']}: {job['failure_reason']}")
        time.sleep(2)


def download(path, filename):
    """File routes redirect to a short-lived storage URL. It needs no keys, so don't send them"""
    redirect = requests.get(f"{BASE}/{path}", headers=HEADERS, allow_redirects=False)
    redirect.raise_for_status()
    with open(filename, "wb") as f:
        f.write(requests.get(redirect.headers["location"]).content)

2. Upload a part

Send an STL, OBJ, 3MF, PLY or glTF file and set units to mm or inches, so the results come back in the right size. The shell examples use jq to pull ids out of the responses.

cURL
PART_ID=$(curl -s https://propermesh.com/api/v0.1/parts \
  -H "x-access-key: $PROPERMESH_ACCESS_KEY" \
  -H "x-secret-access-key: $PROPERMESH_SECRET_KEY" \
  -F "file=@bracket.stl" -F "units=mm" | jq -r .id)
echo $PART_ID
Python
with open("bracket.stl", "rb") as f:
    part = post("parts", files={"file": f}, data={"units": "mm"})
part_id = part["id"]

Sending the file in the request works for files up to about 4 MB. For bigger files (the maximum is in section 6), ask POST /parts/upload-url for an upload URL, PUT the file there, then finish with POST /parts and the upload_token it returned.

3. Read the geometry

Every upload starts a geometry analysis on its own, so there's nothing to start: poll GET /parts/{part_id}/geometry until status is complete. It gives you the volume, surface area, bounding box and whether the mesh is watertight.

cURL
# Run it again until "status" is "complete"
curl -s https://propermesh.com/api/v0.1/parts/$PART_ID/geometry \
  -H "x-access-key: $PROPERMESH_ACCESS_KEY" \
  -H "x-secret-access-key: $PROPERMESH_SECRET_KEY" | jq
Python
geometry = wait_for(f"parts/{part_id}/geometry")
print(geometry["volume"], geometry["surface_area"], geometry["bounding_box_x"])
Response
{
  "id": "6f1c2a9e-…",
  "status": "complete",
  "volume": 12653.1,
  "surface_area": 26862.5,
  "bounding_box_x": 80.0,
  "bounding_box_y": 40.0,
  "bounding_box_z": 22.5,
  "is_watertight": true,
  …
}

4. Find thin walls and download the heatmap

The other analyses all work the same way: start a job, then poll it until status is complete. Thickness analysis flags the parts of the surface thinner than your threshold, which is always in millimeters, whatever units the part has. The result is a PLY mesh colored by thickness that opens in MeshLab or Blender. Parts you upload through the API also show up in the web analyzer.

cURL
# Flag anything thinner than 2 mm
THICKNESS_JOB_ID=$(curl -s https://propermesh.com/api/v0.1/thickness/$PART_ID/start \
  -H "x-access-key: $PROPERMESH_ACCESS_KEY" \
  -H "x-secret-access-key: $PROPERMESH_SECRET_KEY" \
  -H "Content-Type: application/json" -d '{"threshold": 2}' | jq -r .id)

# Run it again until "status" is "complete"
curl -s https://propermesh.com/api/v0.1/thickness/$THICKNESS_JOB_ID \
  -H "x-access-key: $PROPERMESH_ACCESS_KEY" \
  -H "x-secret-access-key: $PROPERMESH_SECRET_KEY" | jq

# The heatmap redirects to a short-lived storage URL. Fetch that URL without your keys
HEATMAP_URL=$(curl -s -o /dev/null -w '%{redirect_url}' \
  https://propermesh.com/api/v0.1/thickness/$THICKNESS_JOB_ID/visual/ply \
  -H "x-access-key: $PROPERMESH_ACCESS_KEY" \
  -H "x-secret-access-key: $PROPERMESH_SECRET_KEY")
curl -s -o heatmap.ply "$HEATMAP_URL"
Python
job = post(f"thickness/{part_id}/start", json={"threshold": 2})  # thinner than 2 mm
job = wait_for(f"thickness/{job['id']}")
print(job["is_thin"], job["thin_surface_area"], job["total_surface_area"])

download(f"thickness/{job['id']}/visual/ply", "heatmap.ply")
Response
{
  "id": "0b7d…",
  "status": "complete",
  "threshold": 2.0,
  "units": "mm",
  "is_thin": true,
  "thin_surface_area": 5860.7,
  "total_surface_area": 26862.5,
  …
}

5. Overhang, orientation and repair

Same pattern: start the job, poll it, download the file.

cURL
# Overhang: faces at or below the critical angle (in degrees) count as overhangs
curl -s https://propermesh.com/api/v0.1/overhang/$PART_ID/start \
  -H "x-access-key: $PROPERMESH_ACCESS_KEY" \
  -H "x-secret-access-key: $PROPERMESH_SECRET_KEY" \
  -H "Content-Type: application/json" -d '{"critical_angle": 45}' | jq

# Optimal orientation: the best way to put the part on the build plate
curl -s -X POST https://propermesh.com/api/v0.1/orient/$PART_ID/start \
  -H "x-access-key: $PROPERMESH_ACCESS_KEY" \
  -H "x-secret-access-key: $PROPERMESH_SECRET_KEY" | jq

# Repair: fixes holes and flipped triangles
curl -s -X POST https://propermesh.com/api/v0.1/repair/$PART_ID/start \
  -H "x-access-key: $PROPERMESH_ACCESS_KEY" \
  -H "x-secret-access-key: $PROPERMESH_SECRET_KEY" | jq

# Each response has an "id". Poll GET /overhang/<id>, /orient/<id> or /repair/<id> the same way,
# then download /overhang/<id>/visual/ply, /orient/<id>/stl or /repair/<id>/stl
Python
overhang = post(f"overhang/{part_id}/start", json={"critical_angle": 45})
overhang = wait_for(f"overhang/{overhang['id']}")
print(overhang["overhang_surface_area"], overhang["num_overhang_faces"])
download(f"overhang/{overhang['id']}/visual/ply", "overhang.ply")

orient = post(f"orient/{part_id}/start")
orient = wait_for(f"orient/{orient['id']}")
download(f"orient/{orient['id']}/stl", "oriented.stl")

repair = post(f"repair/{part_id}/start")
repair = wait_for(f"repair/{repair['id']}")
download(f"repair/{repair['id']}/stl", "repaired.stl")

6. Limits and errors

  • Rate limits. The free beta has daily limits on uploads and analyses, a cap on running jobs, and a storage cap (see pricing). Going over one returns 429 with a Retry-After header (in seconds) and a detail message.
  • Timing. Most jobs finish within seconds; large meshes can take a few minutes. How long to poll, and the exact limits, are under Limits & timing in the API reference.
  • Failed jobs. A job that can't finish has status failed and a failure_reason that says why. The Python wait_for helper raises it as an error.
  • File types. Ask which file types and what maximum file size are accepted with GET /parts/supported-file-types.
Python
response = requests.post(f"{BASE}/thickness/{part_id}/start", headers=HEADERS,
                         json={"threshold": 2})
if response.status_code == 429:
    wait_seconds = int(response.headers["Retry-After"])
    raise SystemExit(f"{response.json()['detail']} Try again in {wait_seconds}s")
response.raise_for_status()

try:
    job = wait_for(f"thickness/{response.json()['id']}")
except RuntimeError as error:
    print(error)  # e.g. "thickness/0b7d… failed: <the failure_reason>"
cURL
curl -s https://propermesh.com/api/v0.1/parts/supported-file-types \
  -H "x-access-key: $PROPERMESH_ACCESS_KEY" \
  -H "x-secret-access-key: $PROPERMESH_SECRET_KEY" | jq

Next

The API reference lists every endpoint, parameter and response.