Skip to main content
This page is for developers who publish data to Speckle from code: scripts calling client.version.create (specklepy) or client.Version.Create (Speckle.Sdk for .NET), older scripts calling the commitCreate GraphQL mutation (including SketchUp-era tooling), and Speckle Automate function authors. If a deprecation warning in your SDK pointed you here, this is the right place. Desktop connector and web app users are not affected.

What changed

Creating a version used to be synchronous: the mutation wrote the version, and you could query it, open its URL, or receive it immediately. Now every version-creating call goes through the same processing pipeline as file uploads, called a model ingestion:
  1. Your call returns immediately with a reserved version id. The response shape is unchanged, so existing code keeps compiling and running.
  2. The server processes your already-uploaded objects in the background (packing, then building the new-format data bundle).
  3. The version is created when processing completes. Only then does it appear in queries and version lists, resolve at its URL, and fire version_created webhooks and subscriptions.
The practical consequence: code that creates a version and immediately reads it back no longer finds it. Fetching the version right after the call returns a not-found error until processing finishes. If processing fails, the version is never created at all, so a failed ingestion is your signal to resend, and you never get a partial or broken version. You do not create this ingestion yourself: on these calls the server creates and drives it, and your job is only to observe it. Creating your own ingestion is part of the upload paths that replace these calls, such as SendPipeline in the .NET SDK.
This applies to app.speckle.systems and to self-hosted servers running v2026.9 or later. It is part of the wider data model migration.

What to call instead

specklepy scripts

client.version.create keeps working and returns the same Version object, but its id is a reserved id: the version does not exist yet.
If your script only publishes and exits, you are done. If it needs the version to exist (to build a link, trigger something downstream, or receive it back), add a wait, for example by polling until the version resolves:
A future specklepy release replaces this pattern with a publish API that runs on the ingestion rail directly.

.NET scripts (Speckle.Sdk)

client.Version.Create keeps working the same way: the returned Version.id is reserved, not yet queryable. The supported successor is the ingestion upload path the desktop connectors use: create an ingestion, run SendPipeline, and let the server create the version when processing completes. The ingestion is your progress and readiness surface, and there is no separate Version.Create call at all.
Example
See Publish large models for the full walkthrough and Model ingestion for the API reference, including client.Subscription.CreateProjectModelIngestionUpdatedSubscription for push-based updates instead of polling. If you build the new-format data bundle yourself, an upcoming Speckle.Sdk release also exposes the v2 upload rail (uploads/complete), where the version is created as soon as your upload completes. Most integrations should use SendPipeline and let the server do the conversion.

Raw GraphQL and v1-era scripts

Both versionMutations.create and the legacy commitCreate mutation (still used by SketchUp-era tooling and v1 scripts) return the id synchronously. Treat it as reserved: do not build “view it online” links, query the version, or hand the id to another system until the ingestion completes. Use the queries and subscription in Wait for the version.

Automate functions

CreateNewVersionInProject (.NET) and create_new_version_in_project (Python) keep working unchanged:
Example
Automation runs that trigger on version_created are unaffected: the trigger already fires only when the version exists. Your function is only affected if it reads back a version it just created in the same run. In that case, wait for it as shown below. An ingestion-backed publish helper for the Automate SDKs is planned; until it ships, the helpers above remain the supported path.

Wait for the version

The ingestion is the readiness surface while your version is being processed. Its lifecycle:
  • queuedprocessingsuccess, failed, cancelled, timeout, or invalidInput
  • While processing, legacy sends report the server-side phases PACKING (raw objects into a packfile) and BUNDLING (packfile into the new-format bundle), with progress.
  • On success, the status carries the version id, and the version now exists.
  • On any failure status, no version is ever created. Fix the problem (or just retry) and resend.
Two ids matter, and they are different signals:
  • ModelIngestion.versionId (top-level field): the reserved id, present from the moment the ingestion is created. It matches the id your create call returned. Use it to find your ingestion, never to decide the version exists.
  • ModelIngestionSuccessStatus.versionId (inside statusData): only present once the ingestion succeeded. This is the signal that the version exists.
Query your model’s latest ingestion and match it to the id your call returned:
If other publishes may hit the same model concurrently, list recent ingestions with model.ingestionHistory instead of latestIngestion and pick the entry whose versionId matches yours. For push-based updates instead of polling, use the projectModelIngestionUpdated subscription with a modelId reference and react when statusData becomes a success or failure status. See Real-time subscriptions for a complete example. version_created webhooks also fire only at completion, so existing webhook consumers need no change: by the time your handler runs, the version is queryable.

No removal date

Legacy send stays accepted. The deprecation warnings mean “no longer the recommended path”, not “scheduled for removal”. Retirement will be criteria-driven and announced well in advance; no date is set. You can keep publishing through the deprecated calls as long as they are accepted, provided your code handles the deferred version visibility described above.

FAQ

The id you got back is reserved, not yet a real version. Add a wait as shown in Wait for the version.
Yes, unchanged. Sending objects and receiving them by object id works exactly as before. Only the version-creating step became asynchronous.
The ingestion ends in a failure status (failed, cancelled, timeout, or invalidInput) and no version is created. Polling the version alone cannot distinguish “still processing” from “failed”, so check the ingestion status when a wait runs long.
It breaks: that mutation now returns the ingestion in processing with the reserved id on the top-level versionId field, not a success status. Read the reserved id from there, then wait for the ingestion to succeed. The mutation was already deprecated; new uploads should complete through the v2 upload rail.
Last modified on August 31, 2026