Skip to main content

Goal

Get a working SpeckleSharp script with the shortest path to load and analyse model data — without learning dependency injection.

What you will build

A script that authenticates, loads the latest version of an existing model, counts elements by category, and prints a summary to the console.

When to use this

Use this page when you write:
  • A one-off C# script or console app
  • A Grasshopper C# Script or custom component
  • A C# cell in a polyglot notebook (Jupyter, Visual Studio, Rider)
  • A small automation that is not a desktop connector
You do not need to understand dependency injection, service lifetimes, or ASP.NET Core. Paste the bootstrap once, then write normal C# against client, operations, and Base objects.
If you are building a production desktop connector (Revit, Rhino, AutoCAD, and similar), start with Dependency injection (connectors) and Publish large models (connectors) instead.
The recommended approach for server-facing AEC automation scripts is Receive2/Send2 — no transport factory required. Paste the bootstrap below, authenticate with a PAT from SPECKLE_TOKEN, and call IOperations methods directly. For Grasshopper object construction without server calls, see Use SpeckleSharp in a Grasshopper C# node. For connector Publish/Load on the canvas, use the Grasshopper connector — the SDK complements those components; it does not replace them.

One-time bootstrap (copy this)

Paste this at the top of your script, notebook cell, or Grasshopper C# component. Run it once per process. This is the canonical bootstrap — other pages link here instead of repeating the full block.
https://mintcdn.com/speckle/VtRPWzmN-ULoLfIv/images/developers/sdks/csharp.svg?fit=max&auto=format&n=VtRPWzmN-ULoLfIv&q=85&s=f2970e3b8bd4a2c7899b46211a42f251Example
using Microsoft.Extensions.DependencyInjection;
using Speckle.Sdk;
using Speckle.Sdk.Api;
using Speckle.Sdk.Credentials;

static class SpeckleBootstrap
{
    static readonly ServiceProvider Provider = new ServiceCollection()
        .AddSpeckleSdk(
            new Application("My Script", "my-script"),
            "1.0.0",
            typeof(Speckle.Objects.Geometry.Point).Assembly
        )
        .BuildServiceProvider();

    public static IOperations Operations => Provider.GetRequiredService<IOperations>();
    public static IAccountFactory AccountFactory => Provider.GetRequiredService<IAccountFactory>();
    public static IClientFactory ClientFactory => Provider.GetRequiredService<IClientFactory>();
}
You can rename SpeckleBootstrap to anything. The point is a single static holder so notebook cells and script functions do not repeat new ServiceCollection() on every run.
There is no new Operations() or import-and-go equivalent. Every public entry point (IOperations, IClientFactory, IAccountFactory) is resolved from the bootstrap container. That is a one-time setup cost, not an ongoing architectural commitment.

Complete example

Load an existing model and count elements by category:
https://mintcdn.com/speckle/VtRPWzmN-ULoLfIv/images/developers/sdks/csharp.svg?fit=max&auto=format&n=VtRPWzmN-ULoLfIv&q=85&s=f2970e3b8bd4a2c7899b46211a42f251Complete example
using Speckle.Objects.Data;
using Speckle.Sdk.Credentials;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Extensions;

var token = Environment.GetEnvironmentVariable("SPECKLE_TOKEN")
    ?? throw new InvalidOperationException("Set SPECKLE_TOKEN");
var projectId = Environment.GetEnvironmentVariable("SPECKLE_PROJECT_ID")
    ?? throw new InvalidOperationException("Set SPECKLE_PROJECT_ID");
var modelId = Environment.GetEnvironmentVariable("SPECKLE_MODEL_ID")
    ?? throw new InvalidOperationException("Set SPECKLE_MODEL_ID");

var account = await SpeckleBootstrap.AccountFactory.CreateAccount(
    new Uri(Environment.GetEnvironmentVariable("SPECKLE_SERVER") ?? "https://app.speckle.systems"),
    token
);
using var client = SpeckleBootstrap.ClientFactory.Create(account);

var versions = await client.Version.GetVersions(modelId, projectId, limit: 1);
var latest = versions.items.First()
    ?? throw new InvalidOperationException("No versions on this model");

var root = await SpeckleBootstrap.Operations.Receive2(
    client.ServerUrl,
    projectId,
    latest.referencedObject!,
    account.token,
    onProgressAction: null,
    cancellationToken: default
);

static string GetCategory(Base obj) =>
    obj is DataObject d && d.properties.TryGetValue("category", out var cat) && cat is string s
        ? s
        : obj["category"] as string ?? obj.speckle_type ?? "Unknown";

var counts = root.Flatten().GroupBy(GetCategory).OrderByDescending(g => g.Count());

Console.WriteLine($"Model: {latest.message}{root.Flatten().Count()} objects");
foreach (var group in counts.Take(15))
    Console.WriteLine($"  {group.Key}: {group.Count()}");
onProgressAction and cancellationToken are for long-running host applications (connectors with progress UI). For scripts, null and default are correct — not placeholders to replace.

How it works

What you wantResolve from bootstrapThen use
GraphQL API (Project, Model, Version, …)IClientFactoryCreate(account)client.Version.GetVersions(...), client.Project.Get(...)
Load / send object graphsIOperationsReceive2, Send2, Serialize
Build an account from a PATIAccountFactoryCreateAccount(serverUrl, token)
Reuse a desktop loginIAccountManager (optional)GetAccounts(), GetDefaultAccount()
See Load and publish model data for when to use Send/Receive with transports instead.

Common mistakes

MistakeFix
Creating a new ServiceProvider on every notebook cell runDefine the static bootstrap once per session
Using Send + IServerTransportFactory in a scriptPrefer Send2/Receive2 for server work
Reading obj["category"] on connector dataUse DataObject.properties for BIM fields
Skipping Speckle.Objects in bootstrap assembliesPass typeof(Point).Assembly when receiving geometry

What to skip (unless you are building a connector)

Page / topicWhy scripts can skip it
Dependency injection (connectors)Deep dive for host apps sharing a container
Publish large models (connectors)SendPipeline — production connector upload only
Working with ProxiesOnly when receiving connector data with groups/instances

Grasshopper and hosted scripts

Speckle connector components (Publish, Load): use the components for transport. Custom analysis in Grasshopper C#: see Use SpeckleSharp in a Grasshopper C# node for DataObject construction; return here for Send2/Receive2.

Polyglot notebooks

  1. Paste the bootstrap in the first cell and run it once per kernel session.
  2. In later cells, call SpeckleBootstrap.Operations, SpeckleBootstrap.ClientFactory, and so on directly.
  3. Store account or client in notebook variables if you reuse them — IClient is IDisposable.

Next Steps

Build your first model analysis tool

Full CSV report walkthrough

Authentication

PAT and environment variables

Load and publish model data

Send2 vs Send decision guide

FAQ

No. Treat AddSpeckleSdk + BuildServiceProvider() as a required preamble — like adding using statements.
Build your first model analysis tool for a complete automation. This page for the bootstrap snippet. Full send/receive tour if you want to understand publishing from scratch.
Last modified on July 10, 2026