Skip to main content
This is 2026.9 preview documentation. Coverage here is incremental: a page exists only where 2026.9 differs or is newly documented.
Use this page to judge what a specklepy script must change for 2026.9. It is not a replacement for the specklepy guides, and it is not a full API reference. Prior to 2026.9, the path is still PAT → operations.receive → walk a Base tree. In 2026.9 that receive is a compatibility shim. The new path is operations.receive3, which returns a disposable Model with columnar properties and typed relations. Dates, compatibility mode, and who must act are on Data model migration for developers. The object-model change itself is on Object model in 2026.9.
Versions published by a 2026.9 connector need specklepy 2026.9.0b3 or later, installed with the bundle extra. An older build 404s on that data. Versions from older connectors keep working on the specklepy you have today. See Who’s affected.
Prefer operations.receive3 for new scripts. operations.receive stays callable on legacy object-graph versions, and on bundle-only versions it returns a projected Base tree.

What changes for a script

Update specklepy first

Install a 2026.9 specklepy that can receive bundle-only versions, including the bundle extra. 2026.9.0b3 is the first public build that does this. A later stable 2026.9.0 replaces the beta pin; the calls on this page do not change.
On a bundle-only 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 ids to operations.receive3, or pass the reference to operations.receive and let specklepy dispatch.

Load a model

Prior to 2026.9, you resolve referenced_object and call operations.receive. In 2026.9, operations.receive3 takes an Account plus project, model, and version ids and returns a disposable 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 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, or 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 yet cannot be read afterwards.

Count and filter objects

Prior to 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 bundle-only versions.

Replace property walks

Prior to 2026.9, nested parameters are dictionaries you walk: obj.properties["x"] then ["y"]. In 2026.9 those paths are flat. PropertyView is the type that replaces that 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, then type, then root-scalar precedence.
  • Indexer (obj["path"]): untyped lookup, same precedence.
  • properties.under("Constraints"): the subtree under that prefix, with the prefix stripped.
  • to_nested(): the old nested-dictionary tree. Allocates. Use only when a caller still requires 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 bundle-only versions. Read the value from a dotted path, not from a metadata wrapper.

Replace Proxy walks

Prior to 2026.9, grouping by level or material means indexing applicationIds and resolving proxy objects lists. In 2026.9 those links are typed relations. See Relations in 2026.9 for the names.
Prior to 2026.9
A missing nested elements tree or Proxy objects list on 2026.9 data is expected. Do not unpack Proxies to rebuild containment.

What operations.receive still does

operations.receive remains callable. It is the compatibility path, not the 2026.9 default.
  • Legacy object-graph versions: behaviour is unchanged. You still pass referenced_object as a content hash.
  • Bundle-only versions: referenced_object is a bundle reference (bundle.<projectId>.<modelId>.<versionId>). A 2026.9 specklepy dispatches on that 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.
  • Older specklepy: fetching that referenced_object from the objects API returns 404 with upgrade guidance. Upgrade specklepy rather than parsing the 404 body.
Receiving a bundle-only 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 remains ordinary supported use.

Publish from a script

Most analysis scripts only receive. If you currently call operations.send then client.version.create, the 2026.9 replacement is operations.send3 with a BundleBuilder. operations.send stays accepted and starts emitting deprecation warnings. There is no removal date. send3 returns a reserved version id immediately. Queries, the version URL, and version_created webhooks fire when ingestion completes. Poll for the version, or watch the project’s model ingestions, if you need to follow up in the same process.

FAQ

Upgrade specklepy first so versions from a 2026.9 connector do not 404. Versions from older connectors keep working on operations.receive. Rewrite to operations.receive3 when you need relations, columnar property lookups, or models that are too large to materialize as a Base tree.
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. That is why operations.receive is the compatibility path for the new format, not the default.
Downloaded parquet files stay under a scratch directory until close runs. Always use a with block. Objects and already-loaded geometry stay in memory after close; geometry you have not touched yet cannot be read afterwards.
Yes for hash-era data, until that path is retired. For bundle-only versions, operations.receive still needs an authenticated ServerTransport so it can dispatch to receive3. Move server-facing scripts to operations.receive3.
On a bundle-only receive, 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 a contracted receive-fidelity change, not a bug in your filter.
Last modified on September 2, 2026