Printago API
POST /v1/parts

Create a new part

Create a part from a model file you have uploaded. Uploading takes three calls: get a signed upload URL, send the file bytes to it, then create the part here with the returned path.

Send Authorization: ApiKey <your-api-key> on the Printago API calls (steps 1, 3 and 4). The upload itself (step 2) goes straight to storage and must not carry it.

1. Get a signed upload URL

curl -X POST https://api.printago.io/v1/storage/signed-upload-urls \
  -H "Authorization: ApiKey $PRINTAGO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "filenames": ["Benchy.3mf"] }'

Each entry in signedUrls has a path (keep it for step 3), an uploadUrl and the HTTP method to use. The URL expires after 5 minutes.

2. Upload the file bytes

curl -X PUT --upload-file Benchy.3mf \
  -H "Content-Type: application/octet-stream" \
  "$UPLOAD_URL"

Send the raw file as the request body using the method from step 1 (PUT). Any Content-Type is accepted.

3. Create the part

Call this endpoint with the path from step 1 in fileUris and set type to match the primary file: stl, 3mf, gcode3mf (a 3MF with sliced G-code inside), gcode, step, scad (OpenSCAD), cadquery or build123d. The file is moved from the upload area into the part's permanent storage, and fileUris in the response holds the new location.

4. Wait for processing

After the file is attached, Printago processes it: it reads metadata and filament channels, renders thumbnails, splits 3MF plates and estimates print time. processingStatus reports where that stands:

processingStatus Meaning
"processing" Still running. processing.stages shows each step (fetch, read-metadata, thumbnails, split-plates, estimate).
null Ready to print.
"failed" Processing stopped. processing.failedStage and processing.error say where and why.

By default this request waits for processing to finish (up to roughly 20 seconds) and returns the ready part. Large files can take longer, in which case the part comes back with processingStatus: "processing". Pass ?async=true to return immediately instead. Either way, poll GET /v1/parts/{id} until processingStatus is no longer "processing".

A part cannot be printed until processingStatus is null. POST /v2/builds rejects parts that are still processing or have failed. To retry a failed part, call POST /v1/parts/retry-processing with its ID, or upload a corrected file and PATCH /v1/parts/{id} with the new uploads: path in fileUris.

Requires: part.create

Query Parameters

async optional
boolean
When true, the response returns as soon as file processing is submitted, with `processingStatus: "processing"`; poll the part until the status clears. When false or omitted, the response waits for processing to finish (bounded — a part still processing after the wait is returned as-is, to be polled).

Request Body

Part data including file URIs and optional material assignments

PartInsertWithMaterials
arrangeableoptional
boolean
assignedMaterialsoptional
Record<string, object[]>

Default material assignments per filament channel, keyed by channel index. Each value is a list of { "variantId" }, { "materialId" }, { "groupId" } or { "materialType" } specs. Builds use these unless they override materials.

customMetadataoptional
Record<string, any>
Construct a type with a set of properties K of type T
descriptionrequired
string
fileUrisrequired
string[]

Files for the part. On create, pass the path values from POST /v1/storage/signed-upload-urls (they start with uploads:). The first file is the primary model and is the one that gets processed. Add more files when the primary file needs them, for example files an OpenSCAD model includes. In responses, these are the files' permanent storage paths.

min items: 1
folderIdoptionalnullable
string
pattern: ^[a-z0-9]{24}$
namerequired
string
overriddenProcessProfileIdrequirednullable
string

ID of a process profile to slice this part with instead of the matched printer's profile. Send null to use the printer's profile.

pattern: ^[a-z0-9]{24}$
parametersrequired
object[]
acceptedContentTypesoptional
string[]
File-parameter-only fields (populated when `type === PartParameterVariableType.File`). Kept optional on the shared interface so scalar-parameter call sites do not need union-narrowing. See `FilePartParameter` for the strict shape. `acceptedContentTypes` is intentionally an OPEN `string[]` (not a narrow union). The customizer emits SVG, but Etsy attachments and direct API order ingestion can carry ANY mime type (PNG / JPG / PDF / AI / etc). Builders enforce kind constraints; the shared type stays open.
defaultoptional
number | string | boolean
defaultUrioptional
string
Optional fallback file URI (e.g. a known SVG in storage).
descriptionoptional
string
labeloptional
string
What the parameter is shown under wherever a customer or an operator fills it in; `name` stays the key everything binds by (SKU options, order inputs, build overrides, and — for a 3MF text slot — the text volume the substitution travels to). A parameter without one is shown under its name. This interface is the one the API's request validators are generated from, and it is a second declaration of the shape in `../part/parameter`. A field added to only one of them is accepted by the code and then pruned off the wire.
maxoptional
number
minoptional
number
multipleoptional
boolean
Whether the slot accepts >1 file. Reserved for forward compat — the Phase 5 resolver assumes single-file per slot for v1.
namerequired
string
optionsoptional
object[]
labeloptional
string
valuerequired
string | number
serialReuseoptional
"share" | "increment"
For a 3MF text-slot parameter bound to a sequence: whether occurrences repeated on one plate share a serial or each consume the next one. Absent means {@link TextSlotSerialReuse.Share }, today's duplicate-name meaning.
shareincrement
sizingoptional
"stretch" | "fit" | "unconstrained"
3MF text placeholders only: how a substituted value is sized against the bounds of the text volume it replaces. Absent means "fit within bounds", which is what every part declared before the setting existed carries. Declared on this shape as well as on `../part/parameter` for the reason `label` is — the API's request validators are generated from this one, and a field on only the other is pruned off the wire.
stretchfitunconstrained
stepoptional
number
typerequired
"number" | "string" | "boolean" | "file"
numberstringbooleanfile
valueoptional
number | string | boolean
printTagsrequired
object
filament.typeoptional
string
printer.idoptional
string
printer.modelNameoptional
string
printer.nozzleDiameteroptional
string
printer.provideroptional
string
user.tagsoptional
string
slicerOverrideoptionalnullable
string
typerequired
enum
scadcadquerybuild123dstlstep3mfgcode3mfgcode
use3MFProcessProfileoptional
boolean

3MF parts only. Slice with the process profile embedded in the 3MF instead of the printer's. When omitted on a 3MF part (and overriddenProcessProfileId is null), it is turned on for you.

userTagsoptional
string[]
max items: 10

Example Request

application/json
{
  "name": "string",
  "type": "scad",
  "description": "string",
  "fileUris": [
    "string"
  ],
  "parameters": [
    {
      "default": 1,
      "options": [
        {
          "value": "string",
          "label": "string"
        }
      ],
      "description": "string",
      "min": 1,
      "max": 1,
      "step": 1,
      "acceptedContentTypes": [
        "string"
      ],
      "multiple": true,
      "defaultUri": "string",
      "label": "string",
      "serialReuse": "share",
      "sizing": "stretch",
      "name": "string",
      "type": "number",
      "value": 1
    }
  ],
  "printTags": {
    "printer.provider": "string",
    "printer.id": "string",
    "printer.nozzleDiameter": "string",
    "printer.modelName": "string",
    "filament.type": "string",
    "user.tags": "string"
  },
  "customMetadata": {},
  "overriddenProcessProfileId": "string",
  "folderId": "string",
  "slicerOverride": "string",
  "use3MFProcessProfile": true,
  "arrangeable": true,
  "userTags": [
    "string"
  ],
  "assignedMaterials": {}
}

Examples

Create a part from an uploaded 3MF

fileUris takes the path returned by POST /v1/storage/signed-upload-urls, including the uploads: prefix. The response below is the part after processing finished (abbreviated).

Request
{
  "name": "Benchy",
  "type": "3mf",
  "description": "",
  "fileUris": [
    "uploads:k3m9x2p7q8r4t6v1w5y0z3a2/x4c8v2b6n0m3k7j1h5g9f2d6/Benchy.3mf"
  ],
  "parameters": [],
  "printTags": {},
  "overriddenProcessProfileId": null
}
Response
{
  "id": "p8f2k6m1q9w3e7r5t0y4u2i6",
  "storeId": "k3m9x2p7q8r4t6v1w5y0z3a2",
  "name": "Benchy",
  "type": "3mf",
  "description": "",
  "fileUris": [
    "k3m9x2p7q8r4t6v1w5y0z3a2/parts/p8f2k6m1q9w3e7r5t0y4u2i6/Benchy.3mf"
  ],
  "fileHashes": [
    "9f2c4e81a7b3d6f05c8e1a4b7d2f9c3e6a0b5d8f1c4e7a2b9d6f3c0e5a8b1d4f"
  ],
  "thumbnailUri": "k3m9x2p7q8r4t6v1w5y0z3a2/parts/p8f2k6m1q9w3e7r5t0y4u2i6/thumbnail.png",
  "parameters": [],
  "printTags": {},
  "overriddenProcessProfileId": null,
  "folderId": null,
  "materials": [
    {
      "index": 0,
      "color": "#00AE42",
      "type": "PLA"
    }
  ],
  "use3MFProcessProfile": true,
  "arrangeable": true,
  "userTags": [],
  "uploadedAt": "2026-09-23T14:02:31.884Z",
  "processingStatus": null,
  "processing": {
    "workflowKey": "p8f2k6m1q9w3e7r5t0y4u2i6-9f2c4e81a7b3d6f0",
    "fileHash": "9f2c4e81a7b3d6f05c8e1a4b7d2f9c3e6a0b5d8f1c4e7a2b9d6f3c0e5a8b1d4f",
    "attempt": 0,
    "startedAt": "2026-09-23T14:02:31.912Z",
    "finishedAt": "2026-09-23T14:02:38.407Z",
    "stages": [
      {
        "key": "fetch",
        "status": "done"
      },
      {
        "key": "read-metadata",
        "status": "done"
      },
      {
        "key": "thumbnails",
        "status": "done"
      },
      {
        "key": "split-plates",
        "status": "done"
      },
      {
        "key": "estimate",
        "status": "done"
      }
    ]
  },
  "createdAt": "2026-09-23T14:02:31.620Z",
  "updatedAt": "2026-09-23T14:02:38.407Z"
}

Create without waiting (`?async=true`)

POST /v1/parts?async=true returns as soon as processing starts. Poll GET /v1/parts/{id} until processingStatus changes.

Request
{
  "name": "Cable Clip",
  "type": "stl",
  "description": "Clip for 6 mm cables",
  "fileUris": [
    "uploads:k3m9x2p7q8r4t6v1w5y0z3a2/t5r9e3w7q1a4s8d2f6g0h3j7/Cable Clip.stl"
  ],
  "parameters": [],
  "printTags": {},
  "overriddenProcessProfileId": null,
  "userTags": [
    "clips"
  ]
}
Response
{
  "id": "c7l1i5p9c3l6i0p4q8w2e5r9",
  "storeId": "k3m9x2p7q8r4t6v1w5y0z3a2",
  "name": "Cable Clip",
  "type": "stl",
  "description": "Clip for 6 mm cables",
  "fileUris": [
    "k3m9x2p7q8r4t6v1w5y0z3a2/parts/c7l1i5p9c3l6i0p4q8w2e5r9/Cable Clip.stl"
  ],
  "thumbnailUri": null,
  "userTags": [
    "clips"
  ],
  "processingStatus": "processing",
  "processing": {
    "workflowKey": "c7l1i5p9c3l6i0p4q8w2e5r9-3b7e0a4c8f1d5b9e",
    "fileHash": "3b7e0a4c8f1d5b9e2a6c0f4b8d1e5a9c3f7b0d4e8a2c6f9b1d5e0a3c7f4b8d2e",
    "attempt": 0,
    "startedAt": "2026-09-23T14:11:05.230Z",
    "stages": [
      {
        "key": "fetch",
        "status": "pending"
      },
      {
        "key": "read-metadata",
        "status": "pending"
      },
      {
        "key": "thumbnails",
        "status": "pending"
      },
      {
        "key": "split-plates",
        "status": "pending"
      },
      {
        "key": "estimate",
        "status": "pending"
      }
    ]
  },
  "createdAt": "2026-09-23T14:11:04.975Z",
  "updatedAt": "2026-09-23T14:11:05.230Z"
}

Response Schema

Part
arrangeableoptional
boolean
createdAtrequired
string
format: date-time
customMetadataoptional
Record<string, any>
Construct a type with a set of properties K of type T
descriptionrequired
string
fileHashesrequired
string[]
min items: 1
fileUrisrequired
string[]

Files for the part. On create, pass the path values from POST /v1/storage/signed-upload-urls (they start with uploads:). The first file is the primary model and is the one that gets processed. Add more files when the primary file needs them, for example files an OpenSCAD model includes. In responses, these are the files' permanent storage paths.

min items: 1
folderIdoptionalnullable
string
pattern: ^[a-z0-9]{24}$
gcodePlateUrisoptionalnullable
Record<string, string>
Construct a type with a set of properties K of type T
idrequired
string
pattern: ^[a-z0-9]{24}$
materialsrequirednullable
object[]
colorrequirednullable
string
pattern: ^#{0,1}[0-9A-F]{6}|[0-9A-F]{8}$
identifieroptionalnullable
string
indexrequired
number
min: 0
nameoptionalnullable
string
skipoptional
boolean
typerequirednullable
string
meshErroroptionalnullable
string
Why tessellating this source failed. Set with `meshUri` cleared, so a file that cannot be meshed is stated once instead of re-converting on every print forever. Replacing the source moves `fileHashes[0]` off this hash, which retires the error without anything having to clear it.
meshSourceHashoptionalnullable
string
`fileHashes[0]` of the source the mesh state describes — the file `meshUri` was tessellated from, or the one `meshError` failed on.
meshUrioptionalnullable
string
Tessellated mesh derived from a boundary-representation source (STEP). The slicer CLI only reads meshes, so this — not `fileUris[0]` — is what the print and estimation paths feed it.
metadataoptionalnullable
object
filamentColorsrequired
string[]
filamentDensitiesoptional
number[]
filamentDiametersoptional
number[]
filamentIdsoptional
string[]
filamentTyperequired
string[]
nozzleDiameterrequired
string
plateRenderSourceHashoptional
string
`fileHashes[0]` of the source that produced the plates' `thumbnailRenderedUri`. Re-extraction carries the renders forward only while this still matches the part's current source; a replaced file leaves them behind rather than showing the previous model's geometry.
platesoptional
object[]
filamentIdsoptional
string[]
filamentMapModeoptional
string
filamentMapsoptional
number[]
gcodeFileoptional
string
idrequired
number
lockedoptional
boolean
nameoptional
string
patternBboxFileoptional
string
pickDataoptional
string
pickFileoptional
string
pickUploadedUrioptional
string
slicedoptional
object
extruderTypeoptional
string
filamentMapsoptional
string
filamentsoptional
object[]
indexoptional
number
labelObjectEnabledoptional
boolean
layerFilamentListsoptional
object[]
nozzleDiametersoptional
string
nozzleVolumeTypeoptional
string
objectsoptional
object[]
outsideoptional
boolean
predictionoptional
number
printerModelIdoptional
string
supportUsedoptional
boolean
timelapseTypeoptional
string
warningsoptional
object[]
weightoptional
number
thumbnailDataoptional
string
thumbnailFileoptional
string
thumbnailNoLightFileoptional
string
thumbnailRenderedUrioptional
string
The plate re-rendered by the three.js builder. Written by a different flow, and to a different storage path, than the archive copy above — a later metadata reprocess re-copies the archive image and must not be able to overwrite this. Prefer it over `thumbnailUploadedUri` (see `plateThumbnailUri`).
thumbnailUploadedUrioptional
string
The archive's own plate preview, copied out at metadata-extraction time.
topDataoptional
string
Base64 PNG data for the top/pick images, staged before upload then cleared.
topFileoptional
string
topUploadedUrioptional
string
Uploaded URIs for the plate's top-down image and color-coded object pick mask. Used by the visual skip-objects picker.
printerModelrequired
string
profileCompatrequired
object
compatiblerequired
string[]
saferequired
string[]
slicedrequired
string
slicerVersionoptionalnullable
object
enginerequired
"orcaslicer" | "bambu-studio" | "prusaslicer" | "superslicer"
orcaslicerbambu-studioprusaslicersuperslicer
versionrequired
string
textSlotsoptional
object[]
PrintagoParam text slots with their per-plate occurrence identities, re-detected whenever the file is (re)processed. The saved reuse mode lives on the slot's parameter, which survives a file replacement; this is the geometry-derived half that must always track the current file.
fontNameoptional
string
`font_name` of the first volume seen with this key.
fontVersionoptional
string
`font_version` of the first volume seen with this key.
keyrequired
string
The matching volume/object name — the substitution key.
labelrequired
string
The label this slot's parameter is shown under.
occurrencesoptional
object[]
Every printable object instance carrying this slot, across all plates. Absent when the file predates occurrence detection or has no plate metadata to resolve instances against.
identifyIdoptional
string
Bambu's per-instance id, when present (matches gcode object labels).
instanceIdrequired
number
0-based instance index among the object's build items.
objectIdrequired
string
`model_settings.config` object id (matches the root model's build items).
plateIdrequired
number
surfaceTypeoptional
number
`surface_type` of the first volume seen with this key (0=flat).
textrequired
string
The text authored into the volume. It prints as-is when the slot goes unfilled, which makes it the parameter's default.
volumeCountrequired
number
How many text volumes in the file carry this slot name.
namerequired
string
overriddenProcessProfileIdrequirednullable
string

ID of a process profile to slice this part with instead of the matched printer's profile. Send null to use the printer's profile.

pattern: ^[a-z0-9]{24}$
parametersrequired
object[]
acceptedContentTypesoptional
string[]
File-parameter-only fields (populated when `type === PartParameterVariableType.File`). Kept optional on the shared interface so scalar-parameter call sites do not need union-narrowing. See `FilePartParameter` for the strict shape. `acceptedContentTypes` is intentionally an OPEN `string[]` (not a narrow union). The customizer emits SVG, but Etsy attachments and direct API order ingestion can carry ANY mime type (PNG / JPG / PDF / AI / etc). Builders enforce kind constraints; the shared type stays open.
defaultoptional
number | string | boolean
defaultUrioptional
string
Optional fallback file URI (e.g. a known SVG in storage).
descriptionoptional
string
labeloptional
string
What the parameter is shown under wherever a customer or an operator fills it in; `name` stays the key everything binds by (SKU options, order inputs, build overrides, and — for a 3MF text slot — the text volume the substitution travels to). A parameter without one is shown under its name. This interface is the one the API's request validators are generated from, and it is a second declaration of the shape in `../part/parameter`. A field added to only one of them is accepted by the code and then pruned off the wire.
maxoptional
number
minoptional
number
multipleoptional
boolean
Whether the slot accepts >1 file. Reserved for forward compat — the Phase 5 resolver assumes single-file per slot for v1.
namerequired
string
optionsoptional
object[]
labeloptional
string
valuerequired
string | number
serialReuseoptional
"share" | "increment"
For a 3MF text-slot parameter bound to a sequence: whether occurrences repeated on one plate share a serial or each consume the next one. Absent means {@link TextSlotSerialReuse.Share }, today's duplicate-name meaning.
shareincrement
sizingoptional
"stretch" | "fit" | "unconstrained"
3MF text placeholders only: how a substituted value is sized against the bounds of the text volume it replaces. Absent means "fit within bounds", which is what every part declared before the setting existed carries. Declared on this shape as well as on `../part/parameter` for the reason `label` is — the API's request validators are generated from this one, and a field on only the other is pruned off the wire.
stretchfitunconstrained
stepoptional
number
typerequired
"number" | "string" | "boolean" | "file"
numberstringbooleanfile
valueoptional
number | string | boolean
printTagsrequired
object
filament.typeoptional
string
printer.idoptional
string
printer.modelNameoptional
string
printer.nozzleDiameteroptional
string
printer.provideroptional
string
user.tagsoptional
string
processingoptionalnullable
object

Progress of the most recent processing run: when it started and finished, the status of each stage (pending, running, done, skipped or failed) and, on failure, failedStage and error.

attemptrequired
number
How many ingests this part has had before this one — a monotonic per-part counter, not a per-file retry count, so no two runs can ever share a workflow key. See {@link nextPartIngestAttempt }.
erroroptional
string
failedStageoptional
"fetch" | "read-metadata" | "thumbnails" | "split-plates" | "estimate"
fetchread-metadatathumbnailssplit-platesestimate
fileHashrequired
string
`fileHashes[0]` this ingest describes; replacing the file retires it.
finishedAtoptional
string
stagesrequired
object[]
attemptsoptional
number
Times the stage has been started, so the UI can say "retrying · attempt N".
erroroptional
string
keyrequired
"fetch" | "read-metadata" | "thumbnails" | "split-plates" | "estimate"
fetchread-metadatathumbnailssplit-platesestimate
statusrequired
"running" | "done" | "pending" | "failed" | "skipped"
runningdonependingfailedskipped
startedAtrequired
string
workflowKeyrequired
string
The workflow run that owns this state. A retry or a file replacement writes a new key first, so a superseded run's writes no longer match and are dropped instead of clobbering the newer run's progress.
processingStatusoptionalnullable
"processing" | "failed"

"processing" while the file is being processed, "failed" if processing stopped, and null once the part is ready to print.

processingfailed
slicerOverrideoptionalnullable
string
slicingEstimateoptionalnullable
object
estimatedPrintTimeSecondsrequired
number
materialsrequired
object[]
coloroptional
string
indexrequired
number
materialTyperequired
string
usedGramsrequired
number
usedMetersrequired
number
platesoptional
object[]
estimatedPrintTimeSecondsrequired
number
materialsrequired
object[]
coloroptional
string
indexrequired
number
materialTyperequired
string
usedGramsrequired
number
usedMetersrequired
number
plateIdrequired
number
totalWeightGramsrequired
number
profilesoptional
object
filamentProfileIdsrequired
string[]
filamentProfileNamesrequired
string[]
machineProfileIdrequired
string
machineProfileNamerequired
string
processProfileIdrequired
string
processProfileNamerequired
string
slicedAtrequired
string
slicerVersionrequired
string
totalWeightGramsrequired
number
sourceoptionalnullable
"google-drive" | "onedrive"
Which integration created this part. Set only by the cloud-storage providers today; null on every other creation path (upload, API, OpenSCAD, …) rather than carrying a synthetic "manual" value, so existing parts need no backfill. Not user-editable — it records what happened, not a preference.
google-driveonedrive
storeIdrequired
string
pattern: ^[a-z0-9]{24}$
thumbnailUrirequirednullable
string
typerequired
enum
scadcadquerybuild123dstlstep3mfgcode3mfgcode
updatedAtrequired
string
format: date-time
uploadedAtoptionalnullable
string
format: date-time
use3MFProcessProfileoptional
boolean

3MF parts only. Slice with the process profile embedded in the 3MF instead of the printer's. When omitted on a 3MF part (and overriddenProcessProfileId is null), it is turned on for you.

userTagsoptional
string[]
max items: 10

Example Response

201 OK — application/json
{
  "name": "string",
  "type": "scad",
  "description": "string",
  "fileUris": [
    "string"
  ],
  "fileHashes": [
    "string"
  ],
  "thumbnailUri": "string",
  "parameters": [
    {
      "default": 1,
      "options": [
        {
          "value": "string",
          "label": "string"
        }
      ],
      "description": "string",
      "min": 1,
      "max": 1,
      "step": 1,
      "acceptedContentTypes": [
        "string"
      ],
      "multiple": true,
      "defaultUri": "string",
      "label": "string",
      "serialReuse": "share",
      "sizing": "stretch",
      "name": "string",
      "type": "number",
      "value": 1
    }
  ],
  "printTags": {
    "printer.provider": "string",
    "printer.id": "string",
    "printer.nozzleDiameter": "string",
    "printer.modelName": "string",
    "filament.type": "string",
    "user.tags": "string"
  },
  "overriddenProcessProfileId": "string",
  "folderId": "string",
  "materials": [
    {
      "index": 0,
      "color": "string",
      "type": "string",
      "skip": true,
      "name": "string",
      "identifier": "string"
    }
  ],
  "slicerOverride": "string",
  "metadata": {
    "printerModel": "string",
    "filamentType": [
      "string"
    ],
    "filamentColors": [
      "string"
    ],
    "filamentDensities": [
      1
    ],
    "filamentDiameters": [
      1
    ],
    "nozzleDiameter": "string",
    "profileCompat": {
      "sliced": "string",
      "compatible": [
        "string"
      ],
      "safe": [
        "string"
      ]
    },
    "slicerVersion": {
      "engine": "orcaslicer",
      "version": "string"
    },
    "plates": [
      {
        "id": 1,
        "name": "string",
        "locked": true,
        "filamentMapMode": "string",
        "filamentMaps": [
          1
        ],
        "gcodeFile": "string",
        "thumbnailFile": "string",
        "thumbnailUploadedUri": "string",
        "thumbnailRenderedUri": "string",
        "thumbnailNoLightFile": "string",
        "thumbnailData": "string",
        "topFile": "string",
        "pickFile": "string",
        "topData": "string",
        "pickData": "string",
        "topUploadedUri": "string",
        "pickUploadedUri": "string",
        "patternBboxFile": "string",
        "sliced": {
          "index": 1,
          "extruderType": "string",
          "nozzleVolumeType": "string",
          "printerModelId": "string",
          "nozzleDiameters": "string",
          "timelapseType": "string",
          "prediction": 1,
          "weight": 1,
          "outside": true,
          "supportUsed": true,
          "labelObjectEnabled": true,
          "filamentMaps": "string",
          "objects": [
            {
              "identifyId": "string",
              "name": "string",
              "skipped": true
            }
          ],
          "filaments": [
            {
              "id": "string",
              "trayInfoIdx": "string",
              "type": "string",
              "color": "string",
              "usedM": 1,
              "usedG": 1
            }
          ],
          "warnings": [
            {
              "msg": "string",
              "level": 1,
              "errorCode": "string"
            }
          ],
          "layerFilamentLists": [
            {
              "filamentList": 1,
              "layerRanges": "string"
            }
          ]
        },
        "filamentIds": [
          "string"
        ]
      }
    ],
    "filamentIds": [
      "string"
    ],
    "textSlots": [
      {
        "key": "string",
        "label": "string",
        "text": "string",
        "volumeCount": 1,
        "fontName": "string",
        "fontVersion": "string",
        "surfaceType": 1,
        "occurrences": [
          {
            "plateId": 1,
            "objectId": "string",
            "instanceId": 1,
            "identifyId": "string"
          }
        ]
      }
    ],
    "plateRenderSourceHash": "string"
  },
  "slicingEstimate": {
    "estimatedPrintTimeSeconds": 1,
    "materials": [
      {
        "index": 1,
        "usedGrams": 1,
        "usedMeters": 1,
        "materialType": "string",
        "color": "string"
      }
    ],
    "totalWeightGrams": 1,
    "profiles": {
      "machineProfileId": "string",
      "machineProfileName": "string",
      "processProfileId": "string",
      "processProfileName": "string",
      "filamentProfileIds": [
        "string"
      ],
      "filamentProfileNames": [
        "string"
      ]
    },
    "slicedAt": "string",
    "slicerVersion": "string",
    "plates": [
      {
        "plateId": 1,
        "estimatedPrintTimeSeconds": 1,
        "materials": [
          {
            "index": 1,
            "usedGrams": 1,
            "usedMeters": 1,
            "materialType": "string",
            "color": "string"
          }
        ],
        "totalWeightGrams": 1
      }
    ]
  },
  "use3MFProcessProfile": true,
  "arrangeable": true,
  "userTags": [
    "string"
  ],
  "gcodePlateUris": {},
  "meshUri": "string",
  "meshSourceHash": "string",
  "meshError": "string",
  "uploadedAt": "2025-01-15T12:00:00.000Z",
  "customMetadata": {},
  "source": "google-drive",
  "processingStatus": "processing",
  "processing": {
    "workflowKey": "string",
    "fileHash": "string",
    "attempt": 1,
    "startedAt": "string",
    "finishedAt": "string",
    "failedStage": "fetch",
    "error": "string",
    "stages": [
      {
        "key": "fetch",
        "status": "running",
        "attempts": 1,
        "error": "string"
      }
    ]
  },
  "id": "string",
  "storeId": "string",
  "createdAt": "2025-01-15T12:00:00.000Z",
  "updatedAt": "2025-01-15T12:00:00.000Z"
}