Skip to main content
Upload a supported file and create a model version using GraphQL and an HTTP upload to blob storage. This guide covers requesting an upload URL, uploading the file, starting conversion, and checking the result.

Prerequisites

  • A personal access token with the streams:write scope
  • A Speckle project, where you have a Can edit role
Get your personal access token from: Avatar → Profile → Personal Access Tokens in the Speckle web app.
For all GraphQL mutations, send the token in a request header named 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:
  1. Ask Speckle Server for a presigned upload URL (GraphQL mutation: fileUploadMutations.generateUploadUrl)
  2. Upload the file directly to blob storage (simple HTTP PUT to the presigned URL)
  3. Create a model (if needed) (GraphQL mutation: modelMutations.create - optional if you already have a model)
  4. Tell Speckle to parse and ingest the file (GraphQL mutation: fileUploadMutations.startFileIngestion)
  5. Check the ingestion status (GraphQL query for the ingestion progress)
Important notes:
  • 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

Request an upload URL from Speckle Server. The response contains the URL and a unique ID for the file. POST /graphql Mutation:
Variables example:
Response example:
Important: Save the 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:
Body:
Important:
  • Blob storage returns an ETag in the headers of the response.
  • The ETag is a 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.
Example curl:
If the response is a successful 200 status code, the response headers will contain an ETag header. Record this value:
Note: When using this ETag in Step 4, it must be passed as a double-quoted string (e.g., "\"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:
Variables example:
Response example:
Store the 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:
Variables example:
Important: The 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:
Note: The 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: Check the ingestion status

Use the ID returned by fileUploadMutations.startFileIngestion.id to check progress. A successful result includes the new version ID; a failed result includes the error reason. POST /graphql Query:
Variables example:
Response example:
In case of an error, the error response would look like:

Upload linked files with a model

Some formats keep part of a model in separate files. To convert those files into the same version as the model, upload them as linked files of the model file. Upload the model file and its linked files in this order:
  1. Request an upload URL for the model file as in Step 1.
  2. Request an upload URL for each linked file. Set linkedToFileType to the extension of the model file, without the dot, such as "edb". Without linkedToFileType, the server refuses a file that is not a model of its own, such as a .msh file.
  3. Upload each file as in Step 2 and record each ETag.
  4. In startFileIngestion, pass the linked files in linkedFiles, each with its fileId and etag. One upload takes at most 100 linked files. For the ETABS file size limits, see Upload an ETABS model.
Self-hosted servers on 2026.9 and earlier do not accept linkedToFileType or linkedFiles.

Example: upload an ETABS model with its results

This script uploads an .edb and the result files that share its name, then starts the ingestion. It needs curl and jq, and a model to upload into.
An ETABS upload without its result files publishes the model without analysis results and reports no error. Upload every result file ETABS saved for the model.
Run it with the path to the .edb. The result files must be in the same folder:
The script prints the ingestion ID. To follow the conversion, use it as in Step 5.

What developers need to know

The upload path does not accept STL, 3MF, 3DS, AMF, DXF, X, E57, IGS/IGES, FBX, PLY, or SLDPRT.
app.speckle.systems supports IFC, RVT, NWC, NWD, DWG, 3DM, and others. See Direct uploads for the current list and the formats that changed in 2026.9.Open-source self-hosted servers support IFC only.
Extended formats on self-hosted deployments require Speckle Enterprise Server and a license key. See File imports setup.
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.
startFileImport has been removed. Use fileUploadMutations.startFileIngestion, which gives more granular status updates and real-time subscription updates, with ingestion queries shared across file import and other Speckle integrations. As of the 2026.9 server release (1 September 2026), calling startFileImport fails with a 400 error (UNSUPPORTED_FILE_TYPE) rather than falling back to the old behavior — there is no compatibility period for this mutation.Update your code to use fileUploadMutations.startFileIngestion and the ingestion status queries in this guide.
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_ENABLED feature 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
In these cases, you may need to use the legacy REST endpoints (/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.
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.
Last modified on November 12, 2026