---
title: "API reference"
description: "Every endpoint of the PreForm API, from the OpenAPI specification the Python SDK is generated from."
url: https://developer.formlabs.com/api-reference/
---

# PreForm API

*v1.0.0-alpha* *OpenAPI 3.1.0*

The PreForm API prepares jobs for Formlabs printers and sends them to
print. PreForm, or PreForm Server, its windowless edition, serves it over
HTTP on your own machine.

From a script or a pipeline, import models; orient, support and lay them
out; check printability; and slice or print the result. It supports
Formlabs SLA and SLS printers;
[`/v1/print-setting/list/`](https://developer.formlabs.com/api-reference/print-settings/list-print-settings.md) lists every
printer and material a server supports.

Every v1 endpoint is under `/v1/`.

**Base URL**

`http://localhost:44388`

PreForm or PreForm Server on the local machine, on the port it was started with: PreForm Server's required `--port` argument, or the port chosen when starting the server from PreForm's File menu.

**Just getting started?**

- [Install the Developer Kit](https://developer.formlabs.com/getting-started/install.md) The Python SDK and PreForm
- [Create your first script](https://developer.formlabs.com/getting-started/quickstart.md) Prepare a part and slice it with the Python SDK
- [PreForm Server in PreForm](https://developer.formlabs.com/getting-started/install.md#install-preform) Start it from the desktop app's File menu
- [Headless PreForm Server](https://developer.formlabs.com/guides/headless-preform-server.md) Start it from a terminal or a service

## How PreForm Server differs from a cloud API

Most web APIs, such as the Formlabs Web API, are stateless REST services:
each request stands alone, and resources live in the provider's
database. PreForm Server is a desktop application with an HTTP
interface. It holds your work in memory between requests and runs
beside your files and printers.

1. **State lives in the server process.** Scenes, models, operations and
   the Dashboard login live in the server's memory and vanish when it
   exits. Save work with [`/v1/scene/save/`](https://developer.formlabs.com/api-reference/scenes/save-form-file.md)
   and reopen it with [`/v1/scene/open/`](https://developer.formlabs.com/api-reference/scenes/open-form-file.md).
   Create or open a scene first; endpoints that act on it take its
   `scene_id`.
2. **Endpoints are actions, not resources.** Each route names an action,
   such as `/v1/scene/model/auto-orient/`, and takes a JSON body, so most
   are `POST`, reads included. A response reports what the action did,
   such as the ids it created or work it left unfinished, not the
   resulting state. Read state with
   [`/v1/scene/get/`](https://developer.formlabs.com/api-reference/scenes/get-scene.md) or
   [`/v1/scene/model/list/`](https://developer.formlabs.com/api-reference/modifying-models/list-models.md).
3. **Changes to a scene run in order.** Requests that change a scene run
   one at a time, in arrival order, and each sees the results of those
   before it. Reads don't wait: they return the scene as the last
   finished request left it.
4. **It runs on your machine.** The server listens on localhost and has
   no API keys: whatever reaches the port can use it, so keep the port
   private to the machine. File paths in requests refer to the server's
   machine, or to its container when it runs in Docker. Endpoints that
   call Formlabs web services also need a Dashboard login
   ([`/v1/login/`](https://developer.formlabs.com/api-reference/authentication/login.md)), and say so in their descriptions.

**Architecture**

*Diagram: Your code calls PreForm Server over HTTP on localhost. The server holds scenes and operations in memory, and reads and writes files on this machine, sends jobs to printers, and calls Formlabs web services.*

## A typical session

Work follows the server's lifetime. Start the server or connect to one
that is running, create a scene, and change it one request at a time.
Each request builds on the state the last one left.

Save the scene to a `.form` file before the server exits; the scene goes
with it.

**A session**

**Python:**

```python
from formlabs_sdk import FormlabsClient

with FormlabsClient() as client:
    # Attach to PreForm, or PreForm Server, on port 44388.
    preform = client.preform.connect()

    # The scene lives in the server's memory...
    scene = preform.create_scene(
        machine_type="FORM-4-0",
        material_code="FLGPGR05",
        layer_thickness_mm=0.1,
    )
    sid = scene.scene_id
    preform.import_models(scene_id=sid, file="part.stl")
    preform.auto_orient_models(scene_id=sid)

    # ...so save it to keep it once the server exits.
    preform.save_form_file(
        scene_ids=[sid], file="part.form"
    )
```

**cURL:**

```bash
curl -X POST "http://localhost:44388/v1/scene/create/" \
  -H "Content-Type: application/json" \
  -d '{
    "machine_type": "FORM-4-0",
    "material_code": "FLGPGR05",
    "layer_thickness_mm": 0.1
  }'
# {"scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", ...}

curl -X POST "http://localhost:44388/v1/scene/model/import/" \
  -H "Content-Type: application/json" \
  -d '{
    "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "file": "C:\\Models\\part.stl"
  }'

curl -X POST "http://localhost:44388/v1/scene/model/auto-orient/" \
  -H "Content-Type: application/json" \
  -d '{
    "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  }'

# Write the scene to disk: it is lost when the server exits.
curl -X POST "http://localhost:44388/v1/scene/save/" \
  -H "Content-Type: application/json" \
  -d '{
    "scene_ids": [
      "3fa85f64-5717-4562-b3fc-2c963f66afa6"
    ],
    "file": "C:\\Models\\part.form"
  }'
```

## Asynchronous requests

Slicing, importing a large CAD file and generating supports can take
minutes. Endpoints that can run long accept `?async=true` and return
`202 Accepted` at once, with an `operation_id` and an `operation_url`
(also in the `Location` header).

Poll the `operation_url` until `status` is `SUCCEEDED` or `FAILED`;
`result` then holds the response the endpoint would have returned
synchronously. Like scenes, operations do not survive a restart.

Without `async`, a call blocks until it finishes or reaches the server's
10-minute timeout.

**Asynchronous request**

**Python:**

```python
from formlabs_sdk import FormlabsClient

with FormlabsClient() as client:
    preform = client.preform.connect()

    # The SDK sends ?async=true and polls for you.
    result = preform.import_models(
        scene_id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
        file="assembly.step",
        on_progress=lambda p: print(f"{p:.0%}"),
        timeout=600,
    )
```

**cURL:**

```bash
curl -X POST "http://localhost:44388/v1/scene/model/import/?async=true" \
  -H "Content-Type: application/json" \
  -d '{
    "scene_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "file": "C:\\Models\\assembly.step"
  }'

# Poll the operation_url until status is SUCCEEDED or FAILED.
curl "http://localhost:44388/v1/operation/get/?operation_id=7c9e6679-7425-40de-944b-e07fc1f90ae7"
```

**Response**

**202 Accepted:**

```json
{
  "operation_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "operation_url": "/v1/operation/get/?operation_id=7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "message": "Operation accepted. Poll GET /v1/operation/get/?operation_id=7c9e6679-7425-40de-944b-e07fc1f90ae7"
}
```

**Polled: SUCCEEDED:**

```json
{
  "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "status": "SUCCEEDED",
  "progress": 1.0,
  "result": {
    "new_models": [{ "model_id": "c2a37eb2-8e4b-4b8e-9f37-1f5f2c1a9d10" }]
  }
}
```

## Errors

The API uses conventional HTTP status codes, and every error body is an
`error` object with a `code` and a `message`.

**Program against `code`, never against `message`.** Codes are stable
SCREAMING_SNAKE_CASE identifiers from the [ErrorCode](https://developer.formlabs.com/api-reference/errors.md)
registry; new ones may appear without a version change, so handle codes
you don't recognize. Messages are for people and may change in any
release.

Each endpoint's *Error codes* table lists the codes it returns and their
causes.

**Error response**

**Python:**

```python
from formlabs_sdk import FormlabsClient, PreFormApiError

with FormlabsClient() as client:
    preform = client.preform.connect()
    sid = "3fa85f64-5717-4562-b3fc-2c963f66afa6"
    try:
        scene = preform.get_scene(scene_id=sid)
    except PreFormApiError as error:
        # Match on the code, never on the message.
        if error.code != "INPUT_ERROR":
            raise
        print("No such scene")
```

**JSON:**

```json
{
  "error": {
    "code": "INPUT_ERROR",
    "message": "Scene with given scene_id not found"
  }
}
```

**HTTP status codes**

| Status | Meaning |
| --- | --- |
| `200 OK` | The request succeeded. |
| `202 Accepted` | Started with `?async=true`. Poll the returned operation. |
| `400 Bad Request` | The request was malformed or referenced something that does not exist. |
| `404 Not Found` | A referenced resource, such as a model or a connected printer, does not exist, or the path is not a v1 endpoint. The code is `NOT_FOUND`. |
| `409 Conflict` | The server's current state prevents the request: a referenced resource is busy with another operation, or the request needs more scenes than the server can hold. |
| `422 Unprocessable Entity` | The request is well-formed and names a real scene, but the scene's current configuration does not support it. |
| `500 Internal Server Error` | Something failed inside the server that the request did not cause. |

## Conventions

### Identifiers

Scenes, models and operations have UUIDs. Responses give them bare
(`3fa85f64-5717-4562-b3fc-2c963f66afa6`); requests also accept the
braced form (`{3fa85f64-...}`).

### Enums

Enum values are SCREAMING_SNAKE_CASE. **Request enums are closed:** a
value outside the list fails with `400 JSON_SCHEMA_MATCH_ERROR`. Later
releases may accept new values, which older servers reject the same way.
**Response enums are open:** schemas that list their values under
`x-extensible-enum`, instead of `enum`, may gain values in any release
without a version change, so treat an unrecognized value as unknown
instead of failing.

### Created models

Requests that add models return their ids, in no particular order. When
they derive from an existing model (copies, split bodies, a drilled
plug), the response names it too, so you never need to diff the scene.

**Enum in a response**

```json
{
  "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "status": "IN_PROGRESS",
  "progress": 0.42
}
```

## Resources

**[Server Info](https://developer.formlabs.com/api-reference/server-info.md)**

Endpoint for checking that the server is up and reachable.

1 endpoint

**[Authentication](https://developer.formlabs.com/api-reference/authentication.md)**

Endpoints for checking and managing your Formlabs Dashboard account login state. Endpoints that call Formlabs web services need a login session, and say so in their descriptions.

3 endpoints

**[Scenes](https://developer.formlabs.com/api-reference/scenes.md)**

Create, load, and enumerate scenes. A scene is the top-level unit of work: it holds a printer/material/print-setting combination and the models placed within that printer's build volume.

12 endpoints

**[Modifying Models](https://developer.formlabs.com/api-reference/modifying-models.md)**

Endpoints that take actions on models within a scene.

24 endpoints

**[Printability Checks](https://developer.formlabs.com/api-reference/printability-checks.md)**

Scene printability checks. These endpoints analyze the scene and report problems. They never modify it.

7 endpoints

**[Print Settings](https://developer.formlabs.com/api-reference/print-settings.md)**

The catalog of machine/material/layer-thickness/print-setting combinations Formlabs ships settings for, and validation of a candidate combination against that catalog.

3 endpoints

**[Operations](https://developer.formlabs.com/api-reference/operations.md)**

Poll and manage the long-running operations started by endpoints called with `?async=true`. `/v1/operation/get/` returns one operation's status and, once finished, its result. See the *Asynchronous requests* section of the introduction.

3 endpoints

**[Devices](https://developer.formlabs.com/api-reference/devices.md)**

Enumerate printers and Fleet Control printer groups PreForm knows about, discover new ones on the network, and upload firmware to them. Discovering Dashboard-registered printers and Fleet Control queues requires a Dashboard login (`/v1/login/`).

3 endpoints

**[Errors](https://developer.formlabs.com/api-reference/errors.md)**

The shared error envelope and the registry of stable error codes. See the *Errors* section of the introduction for the contract.

Reference

**[Schemas](https://developer.formlabs.com/api-reference/schemas.md)**

The 135 request and response schemas.

[Download the OpenAPI specification](https://developer.formlabs.com/reference/openapi-v1.yml)
