Pre-requisites
- A personal access token with the
streams:writescope - A Speckle project, where you have a
Can editrole
Get your personal access token from: Avatar → Profile → Personal Access Tokens in the Speckle
web app.
Authorization:
On app.speckle.systems, this presigned-URL workflow is the default
— no feature flag is required. On self-hosted public Speckle Server releases, set
FF_NEXT_GEN_FILE_IMPORTER_ENABLED=true so the server queues jobs for ifc-import-service. Leave
FF_LEGACY_FILE_IMPORTS_ENABLED off unless you need the legacy REST path — see the FAQ below.Overview
Uploading a file and triggering Speckle’s automatic ingestion pipeline is a multi-step process:-
Ask Speckle Server for a presigned upload URL
(GraphQL mutation:
fileUploadMutations.generateUploadUrl) - Upload the file directly to blob storage (simple HTTP PUT to the presigned URL)
-
Create a model (if needed)
(GraphQL mutation:
modelMutations.create- optional if you already have a model) -
Tell Speckle to parse and ingest the file
(GraphQL mutation:
fileUploadMutations.startFileIngestion) - Check the ingestion status (GraphQL query for the ingestion progress)
- Large files are never sent through the Speckle Server REST endpoints.
- The upload URL points to your server’s configured blob storage (S3, S3 compatible, or Azure).
- The ETag returned from Blob storage is required for step 4 (file ingestion).
- Successful ingestion results in a new Model Version.
Step 1: Generate an upload URL
We start by notifying the Speckle Server of our intent to upload a file. The server responds with a URL, which we will use to upload the file in a subsequent step, and an unique ID for the expected file. POST/graphql
Mutation:
fileId from the response - you must use this exact value in Step 4 when triggering the file ingestion. Do not use the filename; use the fileId returned here.
Step 2: Upload the file to the presigned URL
PUT{presignedURL}
Headers required:
- Blob storage returns an ETag in the headers of the response.
- The ETag is an unique identifier for the uploaded file content, which Speckle uses to verify the file integrity before ingesting the file.
- The ETag is required for the final step.
200 status code, the response headers will contain an ETag header. Record this value:
"\"ad13b92e173...\"").
Step 3: Create a model (if needed)
This step requires a target model, where the file data should be ingested to. If you don’t have a model yet, you can create one programmatically: POST/graphql
Mutation:
model.id as it will be used as modelId in the next step.
Step 4: Trigger the file ingestion
Once the file is uploaded, tell Speckle to parse, convert, index and create a new model version. POST/graphql
Mutation:
fileId must be the exact value returned from Step 1’s generateUploadUrl response. Do not use the filename - use the fileId from that response.
Response example:
status enum signals the job status where queued, processing, success, failed, and cancelled are possible values.
Your file is now in the ingestion pipeline. Once ingested, a new Version will appear under the referenced Model.
Step 5: Getting the ingestion status
Once the ingestion has been started, the file ingestion job receives an ingestion id fromfileUploadMutations.startFileIngestion.id. This id can be used to get the status of the file ingestion job.
POST /graphql
Query:
What developers need to know
File formats supported
File formats supported
For app.speckle.systems: IFC, RVT, NWC, NWD, DWG, DXF, OBJ, STL,
3DM, and others — see Supported Formats.Open-source self-hosted servers support IFC only. Extended formats are also available on Speckle
Enterprise Server — see Supporting additional file types in Direct
Uploads.
Why are different formats supported in self-hosted vs cloud?
Why are different formats supported in self-hosted vs cloud?
Self-hosted open-source servers use open-source dependencies, which limits supported formats.
Formats such as RVT, NWC, NWD, DWG, DXF, and 3DM need proprietary libraries, so they are available
on app.speckle.systems and on Speckle Enterprise Server — not in
the public open-source distribution.
Where did the StartFileImport mutation go?
Where did the StartFileImport mutation go?
The
startFileImport mutation has been deprecated and replaced with
fileUploadMutations.startFileIngestion. The new mutation provides more granular status updates
and allows real-time subscription updates. Ingestion queries are shared across file import and
other Speckle integrations.Update your code to use fileUploadMutations.startFileIngestion and the ingestion status
queries in this guide.Why is this not REST?
Why is this not REST?
Because:
- Files should go direct to blob storage for performance and scale.
- GraphQL mutations model “actions” better than REST for asynchronous workflows.
- GraphQL subscriptions allow real-time updates on the ingestion status, which is not possible with REST.
Should I ever use the old REST upload?
Should I ever use the old REST upload?
For app.speckle.systems, no. The old REST upload is gone.For self-hosted instances, the old REST upload is only relevant if:
- Your server runs with the
FF_LEGACY_FILE_IMPORTS_ENABLEDfeature flag enabled (it is off by default), which proxies uploads through the server instead of sending them straight to blob storage - Your blob storage is not reachable by clients, so presigned URLs cannot work
/api/file/...).
However, we recommend leaving FF_LEGACY_FILE_IMPORTS_ENABLED off and making your S3-compatible storage reachable by clients, so the modern workflow described above works.Can I automate file uploads with SpecklePy?
Can I automate file uploads with SpecklePy?
SpecklePy centres on sending and receiving Speckle objects rather than this
presigned-upload pipeline. You can still implement this guide in Python by combining GraphQL
(
fileUploadMutations.generateUploadUrl, model creation if needed,
fileUploadMutations.startFileIngestion, ingestion status query) with HTTP PUT of file bytes
to the presigned URL via requests or httpx. For GraphQL steps, use
SpeckleClient.execute_query() as in Custom GraphQL
Queries, or POST the same
JSON payloads your tool would send to {host}/graphql.