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 2026.9 or later. It is part of the wider data model migration.
Workspace compatibility mode changes the conversion path used by file uploads and cloud syncs. It does not change object sends completed through version.create or commitCreate; the server still packs and converts those sends into a bundle before creating the version.

What to call instead

specklepy scripts

client.version.create is deprecated but still accepted. It 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:
For new work, build a bundle with BundleBuilder and publish it with operations.send3, which runs on the ingestion rail directly and skips the server-side conversion. See Publish a bundle from your own code.

.NET scripts (Speckle.Sdk)

client.Version.Create is deprecated but still accepted in 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 bundle yourself with a BundleBuilder, Send3 publishes it over the v2 upload rail (uploads/complete), and WaitForVersion returns once the version exists. See Publish a bundle from your own code. Integrations that already produce a Base tree can keep using 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 their signatures in the 2026.9 Automate SDKs (specklepy[bundle]==2026.9.0b3, Speckle.Automate.Sdk 2026.9.0-beta.4 or later). The returned id is reserved:
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. In .NET, CreateNewVersionInProject also accepts a BundleBuilder and publishes it over the ingestion rail; call context.WaitForVersion on its result before the run ends.

Wait for the version

The ingestion is the readiness surface while your version is being processed. Its lifecycle:
  • queued → processing → success, 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

No removal date is set for legacy send. Retirement will be criteria-driven and announced well in advance. Until then, servers with legacy-send support accept the deprecated calls, provided your code handles the deferred version visibility described above. Build new work on bundles: a server without legacy-send support already rejects these calls with LEGACY_SEND_UNSUPPORTED.

FAQ

The id you got back is reserved, not yet a real version. Add a wait as shown in Wait for the version.
Not for new work. operations.send still uploads objects, and only the version-creating step became asynchronous, but publishing an object graph is deprecated. New publishes build a bundle and use operations.send3.
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 October 19, 2026