Skip to main content
Versions published before 2026.9 stay readable. New and updated scripts use the 2026.9 specklepy and operations.receive3: versions published by a 2026.9 connector need that build, and their full data shape only comes through operations.receive3. Speckle does not guarantee that older specklepy builds, or operations.receive, keep working against current servers. This page shows each change side by side so you can judge how much of your script moves. The old path is PAT → operations.receive → walk a Base tree. The 2026.9 path is PAT → operations.receive3, which returns a Model with columnar properties and typed relations. For the data model itself, see Object model. For dates and who must act, see Data model migration for developers.
An older specklepy gets a 404 when it loads a version published by a 2026.9 connector. Install specklepy 2026.9.0b3 or later with the bundle extra before you change any code. See Who’s affected.

What changes for a script

Update specklepy first

Install a 2026.9 specklepy with the bundle extra. 2026.9.0b3 is the first public build that loads 2026.9 versions. A later stable 2026.9.0 replaces the beta pin; the calls on this page do not change.
On a 2026.9 version, version.referenced_object is a bundle reference such as bundle.<projectId>.<modelId>.<versionId>, not a content hash. An older specklepy treats that string as an object id, requests it from the objects API, and receives a 404 whose body tells you to upgrade.
Do not fetch version.referenced_object from the objects REST endpoints. Bundle references always 404 there, including after you upgrade. Pass the project, model, and version ids to operations.receive3.

Load a model

operations.receive3 takes an Account plus project, model, and version ids, and returns a Model. Use it as a context manager: it owns the downloaded bundle files on disk until the with block ends. Auth and version lookup stay as on Authentication and the Quickstart. The snippets below assume account, client, transport, project_id, model_id, and version_id are already in scope. Build account with Account.from_token and a PAT, use client.account after you authenticate, or call get_default_account().
Prior to 2026.9
Close the model (leave the with block, or call model.close()) so the download directory is deleted. Parsed objects stay usable after close. Geometry you have not touched cannot be read afterwards.
operations.receive3 works for every version that has a bundle, including older versions converted by the server. If an older stored version has not been converted, or its conversion could not complete, receive3 raises a SpeckleException. Use operations.receive only while that version’s object graph remains available. On current servers, sends from older connectors are bundled before the new version becomes visible.

Count and filter objects

Before 2026.9 you walk a Base tree. In 2026.9, objects are a flat list. Properties are path-keyed ("category", "Constraints.Base Offset"). Relations are accessors, not proxy lists.
Prior to 2026.9
Look up one object with model.object_by_application_id(id). application_id is the only identity a bundle object has. Do not treat Base.id as a content hash on a 2026.9 version.

Replace property walks

Before 2026.9, nested parameters are dictionaries you walk: obj.properties["x"] then ["y"]. In 2026.9 those paths are flat, and PropertyView replaces the walk. ModelObject.properties is already this view.
  • get_string / get_double / get_bool: typed lookup. None if the path is missing or the value is another type. Instance properties first, then type properties, then root scalars.
  • Indexer (obj["path"]): untyped lookup, same order.
  • properties.under("Constraints"): the subtree under that prefix, with the prefix stripped.
  • to_nested(): the old nested-dictionary tree. Allocates. Use it only when a caller still needs that shape.
Prefer get_string / get_double for a single path. Use under when you need a group. The snippets below assume wall is already in scope: a DataObject from the tree, or a ModelObject from model.objects.
Prior to 2026.9
If the script reads parameter["value"] or other per-parameter metadata from the received tree, that shape is gone on 2026.9 versions. Read the value from a dotted path instead.

Replace proxy walks

Before 2026.9, grouping by level or material meant indexing applicationIds and resolving proxy objects lists. In 2026.9, use the typed relation accessors directly. See Relations for the names.
2026.9
A 2026.9 version has no nested elements tree and no proxy objects lists. Do not unpack proxies to rebuild containment; read the relation accessors instead.

Read versions published before 2026.9

Use operations.receive3 for every version that has a bundle. Keep operations.receive only as a fallback for an older stored version that has no bundle and whose object graph remains available.
  • Versions published before 2026.9: pass referenced_object as a content hash and get the object graph back.
  • Versions published by a 2026.9 connector: referenced_object is a bundle reference (bundle.<projectId>.<modelId>.<versionId>). A 2026.9 specklepy recognizes the prefix and returns a best-effort Base tree (Model.to_base(), DataObject idiom, version = 4 on the root). That projection does not scale to the model sizes the bundle format is built for, and it is not a lossless round trip. It needs an authenticated ServerTransport for the reference’s project. Do not build new work on it.
  • Older specklepy: fetching a bundle reference from the objects API returns 404 with upgrade guidance. Upgrade specklepy rather than parsing the 404 body.
Receiving a 2026.9 version as a Base tree and sending it again with operations.send is not a supported copy workflow. The tree is a baked view, not the authored graph.
Receive, derive new data, then send to a new version remains supported.

Publish from a script

Most analysis scripts only receive. If your script calls operations.send then client.version.create, move it to operations.send3 with a BundleBuilder. Publishing an object graph is deprecated, and Speckle does not guarantee it for new work; see Publish through model ingestions. send3 returns a SendResult with a reserved version_id as soon as the upload finishes. The version appears in queries and at its URL, and fires webhooks, when the server finishes processing. Poll client.version.get if the same script needs the version to exist.
2026.9

FAQ

No, not any longer. Install specklepy[bundle]==2026.9.0b3 and load versions with operations.receive3. Older specklepy builds get a 404 on versions from a 2026.9 connector, and Speckle does not guarantee they keep working against current servers. Keep operations.receive only to read versions published before 2026.9 that have no bundle.
Model is the bundle itself: objects, path-keyed properties, and relation accessors, with geometry parsed only when you ask. A Base tree inflates every object and mesh into Python objects, which is why operations.receive does not suit the new format.
The downloaded parquet files stay in a scratch directory until close runs. Always use a with block. Objects and geometry you already read stay in memory after close; geometry you have not touched cannot be read afterwards.
Only to read versions published before 2026.9. Load everything else with operations.receive3, which takes an account instead of a transport.
On a 2026.9 version, parameter metadata is collapsed to scalars on dotted paths. Read the value with get_double or obj["Width"], not from a metadata wrapper. That is an intended change, not a bug in your filter.
Last modified on October 19, 2026