# Analyse printability

> POST a mesh to /api/printability and get back geometry: watertightness, holes, non-manifold edges, size, and a ready, warning or error verdict. Free, no key.

Source: https://x3dstudios.com/docs/api/printability  ·  Updated: 2026-09-09

POST a mesh file and get the geometry back: is it closed, how many holes, how many branching edges, how big, how much material. It ends with one verdict — ready, warning or error. No account, no key, no credits, and no mesh is returned or stored.

`POST /api/printability`

Analyse an uploaded STL, GLB or OBJ. multipart/form-data, one field.

Auth: public

## Request

**multipart/form-data**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `model` | file | yes | The mesh. STL (binary or ASCII), GLB, GLTF, or OBJ. Format is detected from the glTF magic bytes first, then the file extension, then by sniffing the contents — so an extensionless upload still works. There are no other fields. |

**Anonymous check**

```bash
curl https://x3dstudios.com/api/printability \
  -F model=@bracket.stl
```

| Limit | Anonymous | Signed in |
| --- | --- | --- |
| Checks per hour | 12, keyed on your IP | 60, keyed on your account |
| File size | 25 MB | 50 MB |
| Triangles | 1,500,000 | 1,500,000 |

*The triangle ceiling is checked from the file size before parsing — exactly for a binary STL, estimated elsewhere — so a huge upload is refused rather than parsed.*

> **3MF and G-code are not read here** — This check reads STL, GLB/GLTF and OBJ only. The /print uploader accepts STL, GLB and 3MF, a sliced plate included as .gcode.3mf, and OBJ and bare .gcode are not among them. See /docs/file-formats.

## Response

**200**

```json
{
  "ok": true,
  "filename": "bracket.stl",
  "sizeBytes": 2418084,
  "report": {
    "format": "stl",
    "unitsAssumed": false,
    "triangleCount": 48360,
    "vertexCount": 24182,
    "boundingBoxMm": [82.4, 60.1, 12.6],
    "volumeCm3": 21.44,
    "surfaceAreaCm2": 187.3,
    "watertight": true,
    "boundaryEdges": 0,
    "holes": 0,
    "largestHoleMm": 0,
    "nonManifoldEdges": 0,
    "degenerateFaces": 0,
    "consistentWinding": true,
    "smallestExtentMm": 12.6,
    "issues": [],
    "verdict": "ready"
  }
}
```

| Field | What it is |
| --- | --- |
| format | glb, stl or obj — what the file was read as, which may not match its extension. |
| unitsAssumed | true for GLB and OBJ, false for STL. glTF and OBJ carry no unit declaration; we treat glTF as metres and OBJ as millimetres. Getting this wrong is a factor of 1000, so it is stated rather than hidden. |
| triangleCount | Triangles after the file is read. |
| vertexCount | Distinct vertices after welding — the number topology is measured on, not the count in the file. |
| boundingBoxMm | [x, y, z] in millimetres. |
| volumeCm3 | Signed-tetrahedron sum, absolute. Meaningful only when the surface is closed. |
| surfaceAreaCm2 | Total triangle area. |
| watertight | boundaryEdges is 0 and there is at least one triangle. |
| boundaryEdges | Edges used by exactly one face. |
| holes | Those edges walked into rings — a count of actual holes. |
| largestHoleMm | The widest hole rim, corner to corner. The most legible number in the report. |
| nonManifoldEdges | Edges shared by three or more faces: branching surfaces. |
| degenerateFaces | Zero-area triangles — a repeated index, or area at or below 1e-9. |
| consistentWinding | false when the same directed edge appears twice, meaning some faces point inwards. |
| smallestExtentMm | Smallest overall dimension. Not a wall-thickness measurement. |
| issues | Zero or more { id, severity, title, detail } objects. See below. |
| verdict | ready, warning or error. |

## The verdict

| verdict | Rule | What to do |
| --- | --- | --- |
| ready | No issues at all. | Nothing. Order it. |
| warning | One or more issues, none of them an error. | Read them. Most are things we fix automatically at print time — you can order anyway. |
| error | At least one issue with severity error. Only one issue is ever an error: not-watertight. | The surface has holes. A slicer cannot tell inside from outside through one. We repair this on print, but the result is a guess about what you meant. |

## Every issue it can raise

| id | severity | Raised when |
| --- | --- | --- |
| not-watertight | error | There is at least one open boundary edge, or the file contains no triangles. |
| non-manifold | warning | One or more edges are shared by three or more faces. |
| degenerate | warning | One or more triangles have no area. |
| inconsistent-winding | warning | Some faces are wound the wrong way round and point inwards. |
| thin | warning | The smallest dimension is above zero and under 1 mm. |
| too-large | warning | The longest side is over 340 mm — past the build volume. |
| tiny | warning | The longest side is under 5 mm. On a GLB or OBJ this usually means the file's units are not what we assumed. |

*title and detail are written for a human to read; id is the value to branch on.*

**An issue, as returned**

```json
{
  "id": "not-watertight",
  "severity": "error",
  "title": "3 holes in the surface",
  "detail": "The widest gap measures about 14.2mm across. A slicer cannot tell inside from outside through a hole, so this either fails to slice or prints hollow where it should be solid. We repair this automatically on print."
}
```

> **Overhangs and supports are not part of this check** — It measures topology and size, nothing about orientation or overhang angle. Supports are decided by the slicer at print time and applied automatically. A ready verdict says the file is sound, not that the shape will print unsupported.

## Errors

| Status | Message |
| --- | --- |
| 429 | You've hit the hourly limit for free checks. Try again later, or sign in for more. — with a Retry-After header. |
| 400 | Attach a model file as `model`. |
| 400 | That file is empty. |
| 413 | That file is 31MB. The limit is 25MB — signing in raises it. (Signed in, the same message names the 50MB limit and drops the tail.) |
| 413 | That mesh has roughly 4,182,000 triangles, past the 1,500,000 this check handles. Decimate it and try again. |
| 415 | That does not look like an STL, GLB or OBJ. Those are the formats this check reads. |
| 422 | No triangles found in that file. |
| 422 | Could not read that file. It may be corrupt or use a feature this check does not support. |

*Every failure is a flat { "error": "…" }. Only the 200 carries ok, filename, sizeBytes and report.*

---

- [Printability in Studio](https://x3dstudios.com/docs/studio/printability) — The same check with a picture of your mesh attached.
- [File formats](https://x3dstudios.com/docs/file-formats) — What each part of X3D reads and writes.
- [Text and image to 3D](https://x3dstudios.com/docs/api/generate) — Generated models are measured on the way out — no separate call needed.

## Frequently asked

**Do I need an API key or credits to use /api/printability?**

Neither. The endpoint takes no authentication and costs no credits. Signing in only raises the limits: 60 checks an hour and a 50 MB file instead of 12 an hour and 25 MB.

**Does it return a repaired file?**

No. It returns measurements and a verdict, never a mesh. Repair is a separate, heavier step and it is applied automatically when you order a print, so a warning verdict does not mean we cannot print it.

**Why does my valid STL come back watertight when other tools say it is not?**

Because we weld first. A binary STL stores three fresh vertices per triangle and shares none of them, so any topology check that skips welding calls every correct STL not watertight. We weld, then count boundary edges.

**Is smallestExtentMm my wall thickness?**

No. It is the smallest dimension of the whole bounding box. True wall thickness needs a distance field, which this check does not compute. It is an upper bound on feature size and nothing more.
