> ## Documentation Index
> Fetch the complete documentation index at: https://docs.speckle.systems/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> These docs contain multiple Speckle product experiences. For Speckle 2026.9, prefer /next/ pages for the topics and changes they cover. The navigation label Current does not override this version-specific precedence.
> Coverage under /next/ is incremental. Use Current documentation for unchanged topics and when a /next/ page says coverage is unavailable or explicitly refers you to Current. Do not infer a feature was removed from a missing page.
> Read the page-specific documentation status, applicability, affected guidance and replacement links in agent-only content. Impacted, changed and superseded apply only to the stated scope; they do not mean the entire feature is deprecated. Historical connector guides apply only to the legacy connector described.
> Match guidance to the customer's deployment version, model data format and connector. Respect plan, permission and compatibility restrictions. If an unknown version or data format changes the answer, ask a focused clarification; do not infer deployment version from the date alone.

# Load a bundle in your own code

> How a consumer lists a version's artifacts, downloads the bundle, and rebuilds objects, geometry, instances, grouping, and appearance without a Speckle SDK.

Documentation set: Speckle 2026.9. Status: current. Authority: canonical for this version and the coverage on this page.

Applies to version 2026.9, from 2026-09; this is not a rollout date for every deployment.

Authoritative Speckle 2026.9 documentation for the topic covered here. For Speckle 2026.9, prefer this page over conflicting earlier guidance. Follow the linked pages in the other documentation version for unchanged topics and where this page says coverage is not yet available. Respect data-format, deployment, plan, permission, and compatibility limitations stated in the page.

<Note>
  This is 2026.9 documentation. Coverage here is incremental: a page exists only where 2026.9
  differs or is newly documented. For any other topic, switch to **Current** (2026.8 and earlier) in
  the version selector.
</Note>

If you load versions without a Speckle SDK, there is no root object to download and no graph to
walk. You list the version's artifacts, fetch the parquet files you need from presigned URLs, and
rebuild the model from three key spaces joined by relations. A loader that downloads
`referencedObject` from the objects endpoints gets a 404 on every 2026.9 version. This page
assumes the model from [Building integrations in 2026.9](/next/developers/building-integrations).

## Tell the two formats apart

Any version you are asked to load may still be an object graph from 2026.8 or earlier. Read the version record
first.

| Field                      | 2026.8 and earlier             | 2026.9 bundle                              |
| -------------------------- | ------------------------------ | ------------------------------------------ |
| `Version.schemaVersion`    | `null`                         | `3`                                        |
| `Version.referencedObject` | A content hash                 | `bundle.<projectId>.<modelId>.<versionId>` |
| Where the data is          | The objects endpoints, as JSON | The artifacts endpoint, as parquet files   |

Dispatch on the `bundle.` prefix. Keep your existing reader for the first shape, or refuse it with a
clear message the way the Archicad connector does. Never derive storage paths from the triple in the
reference; resolve the version through the project and version ids you already hold.

## How a load fetches a version

<Steps>
  <Step title="Resolve the version">
    Take the version id from the model URL, or list the model's versions and take the latest. The
    outcome is a project, model, and version id, and a version record whose `schemaVersion` is `3`.
  </Step>

  <Step title="List the artifacts">
    Get the version's artifacts endpoint with your Speckle token. The outcome is one entry per file
    with a presigned URL and an expiry, and the version's viewer `.dat` in the list.
  </Step>

  <Step title="Download the files you need">
    Get each URL without your Speckle token. Skip the geometry shards when you only read properties,
    and skip the `.dat`. The outcome is a bundle directory on local disk.
  </Step>

  <Step title="Read the catalogs first">
    Open `meta`, `rel_types`, and `node_kinds`. The outcome is the vocabulary this bundle was
    written with, including relation namespaces and any ids your reader does not know.
  </Step>

  <Step title="Rebuild the model">
    Intern objects, nodes, and geometry by their dense keys, then apply relations in the order the
    rules below give. The outcome is objects with properties, geometry, placement, grouping, and
    appearance.
  </Step>

  <Step title="Mark the version received">
    Call the mark-received mutation with your application slug. The outcome is a receive recorded
    against the version, the way every Speckle connector does.
  </Step>
</Steps>

The SDK tabs run the whole sequence in one call. The GraphQL and REST tabs are the calls a
non-SDK consumer makes, numbered to match the steps.

<Tabs>
  <Tab title="GraphQL">
    ```graphql theme={null}
    # 1. Read the version record and dispatch on its shape.
    query VersionShape($projectId: String!, $modelId: String!, $versionId: String!) {
      project(id: $projectId) {
        model(id: $modelId) {
          version(id: $versionId) {
            id
            schemaVersion
            referencedObject
            sourceApplication
            message
          }
        }
      }
    }

    # 6. Record the receive.
    mutation MarkReceived($input: MarkReceivedVersionInput!) {
      versionMutations {
        markReceived(input: $input)
      }
    }
    ```

    ```json Variables for MarkReceived theme={null}
    {
      "input": {
        "projectId": "<projectId>",
        "versionId": "<versionId>",
        "sourceApplication": "my-consumer"
      }
    }
    ```
  </Tab>

  <Tab title="REST">
    ```http theme={null}
    # 2. List. Use the model-level path without a version id for the latest version.
    GET /api/v2/projects/{projectId}/models/{modelId}/versions/{versionId}/artifacts
    Authorization: Bearer <token>

    200 {
      "files": [
        { "name": "<versionId>.envelope.meta.parquet", "url": "https://...", "expiresAt": "..." },
        { "name": "<versionId>.geometries.parquet", "url": "https://...", "expiresAt": "..." },
        { "name": "<versionId>.viewer.dat", "url": "https://...", "expiresAt": "..." }
      ]
    }
    404 when the version has no bundle, or the token cannot read the project.

    # 3. Download. No Authorization header on presigned URLs.
    GET <presigned url>
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    from specklepy.api import operations

    with operations.receive3(account, project_id, model_id, version_id) as model:
        print(model.units, len(model.objects), model.unknown_relations)
        for obj in model.objects_with("category"):
            print(obj.application_id, obj.name)
    ```
  </Tab>

  <Tab title=".NET">
    ```csharp theme={null}
    using var model = await SpeckleBootstrap.Operations.Receive3(
        account, projectId, modelId, versionId, cancellationToken: ct
    );
    Console.WriteLine($"{model.Units} {model.Objects.Count} {model.UnknownRelations.Count}");
    ```
  </Tab>
</Tabs>

<Note>
  Presigned URLs expire after an hour and are not content-addressed. Never persist one. Cache the
  downloaded files by version id and revalidate with the ETag when you refetch.
</Note>

<Note>
  The `.dat` in the listing is the viewer's artifact, and a `geometryStream` field beside the file
  list is the viewer's streaming endpoint. Both are internal. Read the parquet files.
</Note>

## Rules that decide whether your load is correct

The bundle tells you its own vocabulary. Read it, and apply these rules in order.

### Reading the files

* Read columns by name and tolerate columns you do not know. The spec adds columns without moving
  the version, so a bundle written before a column existed must still load.
* Read every geometry shard, `{id}.geometries.parquet` and `{id}.geometries.{n}.parquet`. A reader
  that opens only shard zero silently drops geometry above the shard cap.
* Treat a relation id or node kind missing from your vocabulary as skip-and-report, never as an
  error. A newer producer than your reader is normal.
* Do not gate on `meta.schema_version`. It is provenance, not a compatibility switch.

### Identity

* A bare integer is meaningless without its namespace. The relation type's source and target
  namespaces in `rel_types` say whether an end is an object, a node, or a geometry.
* The object's identity is `applicationId`. If you project the bundle into a tree with ids on
  nodes, mint synthetic ids per bundle and expect them to differ across versions.

### Properties

* Each row sets exactly one of the string, number, and boolean columns. Coalesce them.
* Rebuild nesting from the dotted path. `properties.Constraints.Base Offset` is a nested
  dictionary, not a flat key.
* Merge type-level rows into each object through the object-to-type link. Without that merge, most
  type parameters are missing.
* The root scalars `speckle_type`, `name`, `units`, and `type` sit beside `properties.*`, not
  under it.

### Geometry

* Verify the CRC and decode by primitive type. See [Geometry encoding in
  2026.9](/next/developers/object-model/geometry-encoding).
* `DISPLAY` is what to render. Prefer a `SOLID` only when your host can read its format, and fall
  back to the display mesh otherwise. Never draw `CENTERLINE` as a body.
* Convert with the blob's own unit, not a model-wide unit. Objects, instances, and geometry can
  disagree.

### Instancing

* Expand a definition once through `DEFINES`, then place it for every `DISPLAY_INSTANCE` edge using
  the instance node's transform, 16 row-major doubles in the instance's units. Scale only the
  translation when you convert units. `DEFINES_INSTANCE` nests one definition in another.
* An object with no render edge is a carrier for a definition member's properties and grouping.
  Skip it when you bake geometry, or the member draws twice, once untransformed at the origin.
  `DEFINES_MEMBER` and `PLACES` join carriers to their definition and placement.

### Grouping

* Build the tree from the default scene view. Each tier is either a relation such as `IN_MODEL` or
  `IN_COLLECTION`, or a property path such as `category`. With no scene view, group by
  `IN_COLLECTION`.
* Nest containers through their parent reference column, not through a relation.
* `IN_COLLECTION` is single-valued. `IN_GROUP` and `IN_SYSTEM` are not.

### Appearance

* Material fills from specific to general: `HAS_MATERIAL` on the geometry, then
  `OBJECT_HAS_MATERIAL` on the object, then `NODE_HAS_MATERIAL` on the object's container.
* Color is presentational: `OBJECT_HAS_COLOR` overrides `HAS_COLOR` on the geometry, and
  `NODE_HAS_COLOR` is the container default.
* `HAS_COLOR` starts from a geometry or an object, and `ord` says which. Read it before you
  resolve the source key.
* Appearance edges are single-valued per source. When a bundle repeats one, the last row wins.

### Placement

* Read `modelPlacement.*` and `referencePoint.*` from the model-scoped properties. When
  `appliedToGeometry` is true, stored coordinates are already in that datum. When it is false,
  apply the transform yourself. No rows means the internal origin.

## Suggested build order

<Steps>
  <Step title="List and download">
    Authenticate, resolve a version, list its artifacts, and download the non-geometry files. The
    outcome is a bundle directory and a passing read of the three catalogs.
  </Step>

  <Step title="Read properties only">
    Objects by `applicationId`, nested properties from paths, type-level rows merged in. The outcome
    is a property table you can filter, before any geometry.
  </Step>

  <Step title="Decode display meshes">
    Download the shards, verify CRCs, decode meshes, attach them through `DISPLAY` with the right
    units. The outcome is a model that renders untransformed geometry correctly.
  </Step>

  <Step title="Expand instances">
    Definitions, placements, nested definitions, and carrier suppression. The outcome is repeated
    objects in the right places, each drawn once.
  </Step>

  <Step title="Apply grouping and appearance">
    The default scene view, container nesting, and the material and color precedence. The outcome is
    a tree and a look that match the Speckle viewer.
  </Step>

  <Step title="Apply placement and mark received">
    The reference point contract, then the mark-received call. The outcome is geometry in the right
    datum and a receive recorded on the version.
  </Step>
</Steps>

## FAQ

<AccordionGroup>
  <Accordion title="What happens if the artifacts listing returns 404?">
    Either the version is an object graph from 2026.8 or earlier, the bundle has not been produced
    yet, or your token cannot read the project. Check `schemaVersion` on the version record first,
    then the ingestion status if the version is new.
  </Accordion>

  <Accordion title="Can I read properties without downloading geometry?">
    Yes. Geometry is the bulk of a bundle and lives in its own shards. Download the `eav` and
    `envelope` files only. The SDKs expose the same choice as an include-geometry option.
  </Accordion>

  <Accordion title="Can I edit a received bundle's values and publish it again?">
    Only as a new version, and every publish is one. Files are stored under the reserved version id,
    so the server never sees "the same bundle". Your choice is how to make the new file set. Copying
    the received files, rewriting the property rows you changed, stamping `meta` with your own
    producer, renaming to the new id, and uploading is valid when only values change: keys,
    relations, and geometry stay consistent. Build a new bundle when structure changes. Either way
    the version's source application is you. Speckle's own parameter editing applies changes in the
    host and republishes from there, and a receive-then-`send` round trip through a `Base` tree is
    unsupported.
  </Accordion>

  <Accordion title="Do I have to mark the version received?">
    No, but every Speckle connector does. It is how the web app and analytics know a version was
    loaded, and it costs one mutation.
  </Accordion>

  <Accordion title="What if the bundle uses a relation my reader does not know?">
    Skip its edges and report which ids you skipped. The SDKs surface the same set as unknown
    relations. Do not fail the load and do not guess a meaning from the number.
  </Accordion>
</AccordionGroup>
