Skip to main content
There is no dedicated REST endpoint for uploading IFC, DWG, OBJ, or any other file types. The supported programmatic workflow is a hybrid GraphQL + REST (or S3) flow. This section documents the entire mechanism so developers do not have to reverse engineer the server.

Pre-requisites

  • 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, make sure to send the token in as 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

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:
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 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.
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: Getting the ingestion status

Once the ingestion has been started, the file ingestion job receives an ingestion id from fileUploadMutations.startFileIngestion.id. This id can be used to get the status of the file ingestion job. POST /graphql Query:
Variables example:
Response example:
In case of an error, the error response would look like:

What developers need to know

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.
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.
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.
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.
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 August 12, 2026