| name | speckle-impl-sharp-sdk |
| description | Use when writing C#/.NET code with Speckle.Sdk to send, receive, or query Speckle data. Prevents old vs new SDK confusion, missing IL Repack dependency isolation, and incorrect DI registration. Covers Speckle.Sdk NuGet installation, old vs new SDK migration, Client/Account setup, Operations (Send/Receive/Serialize/Deserialize), Helpers (simplified API), IL Repack, Speckle.Objects domain model, and dependency injection patterns. Keywords: speckle sdk, csharp, dotnet, Speckle.Sdk, nuget, Operations.Send, Operations.Receive, Helpers, IL Repack, Speckle.Objects, C# integration, .NET Speckle, build connector.
|
| license | MIT |
| compatibility | Designed for Claude Code. Requires .NET Standard 2.0+, Speckle.Sdk (latest), Speckle Server 2.x/3.x. |
| metadata | {"author":"OpenAEC-Foundation","version":"1.0"} |
speckle-impl-sharp-sdk
Quick Reference
NuGet Packages
| Package | Purpose | Required |
|---|
Speckle.Sdk | Core: Send/Receive, Serialization, API client, Transports | YES |
Speckle.Objects | Domain model classes (geometry, BIM, structural) | YES for AEC data |
Speckle.Sdk.Dependencies | IL Repack dependency isolation for plugin environments | YES for connectors |
Speckle.Automate.Sdk | Speckle Automate function development | Only for Automate |
Target Framework
| Requirement | Value |
|---|
| Target | .NET Standard 2.0 |
| Development SDK | .NET 8.0.4xx |
| Tested platforms | Windows, macOS |
| License | Apache-2.0 |
| Repository | specklesystems/speckle-sharp-sdk |
Critical Warnings
NEVER use the legacy speckle-sharp packages (Speckle.Core, old Speckle.Objects). They are DEPRECATED. ALWAYS use the new Speckle.Sdk packages from the speckle-sharp-sdk repository.
NEVER call .Result or .Wait() on async Send/Receive operations in UI applications. This causes deadlocks. ALWAYS use await.
NEVER build a connector for a host application (Revit, Rhino, etc.) without using Speckle.Sdk.Dependencies for dependency isolation. Version conflicts WILL crash the host application.
NEVER omit [DetachProperty] on large nested objects (meshes, display values). Without detachment, the entire child object is serialized inline, causing bloated payloads and severe performance degradation.
NEVER forget to pass a CancellationToken to Operations.Send() and Operations.Receive() in interactive applications. Without it, there is no way to gracefully cancel long-running operations.
NEVER hardcode personal access tokens in source code. ALWAYS use environment variables, secret managers, or the Speckle Manager account system.
Old vs New SDK: Migration Guide
DEPRECATED: speckle-sharp (Legacy)
The original C# SDK lived in the speckle-sharp repository with monolithic architecture:
| Legacy Package | Status | Replacement |
|---|
Speckle.Core | DEPRECATED | Speckle.Sdk |
Speckle.Objects (old) | DEPRECATED | Speckle.Objects (new, from speckle-sharp-sdk) |
Speckle.DesktopUI | DEPRECATED | Not replaced (connector-specific UI) |
CURRENT: speckle-sharp-sdk
The new SDK focuses on clean dependency boundaries, .NET Standard 2.0 targeting, and IL Repack isolation for safe embedding in host applications.
Migration checklist:
- Remove all
Speckle.Core NuGet references
- Install
Speckle.Sdk and Speckle.Objects from NuGet
- Update namespace imports:
Speckle.Core.* → Speckle.Sdk.*
- For connectors: add
Speckle.Sdk.Dependencies for IL Repack isolation
- Verify all
Operations.* calls compile against the new signatures
Installation
<ItemGroup>
<PackageReference Include="Speckle.Sdk" Version="*" />
<PackageReference Include="Speckle.Objects" Version="*" />
<PackageReference Include="Speckle.Sdk.Dependencies" Version="*" />
</ItemGroup>
dotnet add package Speckle.Sdk
dotnet add package Speckle.Objects
Client and Account Setup
Account Creation (Manual: No Speckle Manager)
var account = new Account();
account.token = "YOUR-PERSONAL-ACCESS-TOKEN";
account.serverInfo = new ServerInfo { url = "https://app.speckle.systems/" };
Account from Environment Variable
var account = new Account
{
token = Environment.GetEnvironmentVariable("SPECKLE_TOKEN"),
serverInfo = new ServerInfo
{
url = Environment.GetEnvironmentVariable("SPECKLE_SERVER_URL")
?? "https://app.speckle.systems/"
}
};
Client Initialization
var client = new Client(account);
The Client class wraps the Speckle Server GraphQL API. It provides methods for managing projects (streams), models (branches), versions (commits), and user operations.
Account from Speckle Manager
When Speckle Manager is installed, accounts are stored locally and can be retrieved:
var account = AccountManager.GetDefaultAccount();
var accounts = AccountManager.GetAccounts();
var accounts = AccountManager.GetAccounts("https://app.speckle.systems");
Operations API
All operations in Speckle.Sdk are async. ALWAYS use await.
Send
var transport = new ServerTransport(account, streamId);
var (rootObjId, convertedRefs) = await Operations.Send(
baseObject,
new ITransport[] { transport },
onProgressAction: progress,
cancellationToken: cts.Token
);
Parameters:
value (Base) — the root object to send
transports (IReadOnlyCollection<ITransport>) — destination transports (MUST NOT be empty)
onProgressAction (IProgress<ProgressArgs>?) — optional progress callback
cancellationToken (CancellationToken) — optional cancellation support
Returns: Tuple of (string rootObjId, IReadOnlyDictionary<string, ObjectReference> convertedReferences)
Flow:
- Validates inputs (non-null value, non-empty transports)
- Calls
BeginWrite() on all transports
- Serializes the object tree via
SpeckleObjectSerializer
- Writes serialized objects to ALL transports simultaneously
- Calls
EndWrite() on all transports (in finally block)
- Returns root object hash and converted references
Receive
var transport = new ServerTransport(account, streamId);
var receivedObject = await Operations.Receive(
objectId,
remoteTransport: transport,
localTransport: null,
onProgressAction: progress,
cancellationToken: cts.Token
);
Parameters:
objectId (string) — hash of the root object
remoteTransport (ITransport?) — source transport (typically ServerTransport)
localTransport (ITransport?) — cache transport (defaults to SQLiteTransport)
onProgressAction (IProgress<ProgressArgs>?) — optional progress callback
cancellationToken (CancellationToken) — optional cancellation support
Returns: Base — the deserialized root object with all children
Flow:
- If
localTransport is null, creates default SQLiteTransport
- Checks local transport first (cache-first strategy)
- On cache miss, calls
CopyObjectAndChildren() on remote to populate local cache
- Deserializes from local transport
- Returns reconstructed
Base object
Serialize / Deserialize
string json = Operations.Serialize(baseObject);
Base deserialized = await Operations.DeserializeAsync(json, cancellationToken);
Helpers: Simplified API
The Helpers class provides a streamlined API for common operations:
await Helpers.Send(streamUrl, baseObject, account);
var received = await Helpers.Receive(streamUrl, account);
Helpers automatically resolve the stream URL into server address, stream ID, and branch/commit references. Use Helpers for quick prototyping and scripts. Use the full Operations API for production code requiring progress reporting, cancellation, and multi-transport patterns.
ServerTransport
var transport = new ServerTransport(
http,
activityFactory,
account,
streamId,
timeoutSeconds: 60,
blobStorageFolder: null
);
Key behaviors:
- Uses a background
SendingThreadMain() thread for upload processing
- Implements server-side deduplication via
HasObjects() before uploading
- Separates blob uploads from object uploads
BeginWrite() starts the background sending thread
WriteComplete() polls until all queued data has been uploaded
EndWrite() terminates the sending thread
The stream_id parameter corresponds to the project ID in current Speckle terminology. Projects were formerly called streams.
Base Class
The Base class is the foundation of all Speckle objects. It behaves as "a dictionary with added Speckle smarts."
Property Attributes
| Attribute | Purpose | When to Use |
|---|
[DetachProperty] | Stores object separately, referenced by hash | ALWAYS for large nested objects (meshes, display values) |
[Chunkable(size)] | Splits large lists into chunks during serialization | ALWAYS for large arrays (vertices, faces, colors) |
[SchemaInfo(name, desc)] | Metadata for schema generation | Documentation and tooling |
Dynamic Properties
var obj = new Base();
obj["customProperty"] = "value";
obj["nestedObject"] = new Base();
obj["numbers"] = new List<double>();
Flatten Extension
using Speckle.Sdk.Models.Extensions;
IEnumerable<Base> allObjects = rootObject.Flatten();
Speckle.Objects Domain Model
The Speckle.Objects package provides typed classes for AEC data:
Geometry
| Class | Namespace | Key Properties |
|---|
Point | Speckle.Objects.Geometry | x, y, z |
Vector | Speckle.Objects.Geometry | x, y, z |
Line | Speckle.Objects.Geometry | start, end |
Polyline | Speckle.Objects.Geometry | value (flat coordinate list) |
Curve | Speckle.Objects.Geometry | Various curve types |
Mesh | Speckle.Objects.Geometry | vertices, faces, colors, textureCoordinates |
Brep | Speckle.Objects.Geometry | Surface representation |
Plane | Speckle.Objects.Geometry | origin, normal, xdir, ydir |
Box | Speckle.Objects.Geometry | basePlane, xSize, ySize, zSize |
Circle | Speckle.Objects.Geometry | plane, radius |
Arc | Speckle.Objects.Geometry | plane, radius, startAngle, endAngle |
Built Environment
| Class | Namespace | Key Properties |
|---|
Wall | Speckle.Objects.BuiltElements | height, baseLine, displayValue |
Floor | Speckle.Objects.BuiltElements | outline, displayValue |
Beam | Speckle.Objects.BuiltElements | baseLine, displayValue |
Column | Speckle.Objects.BuiltElements | baseLine, displayValue |
Room | Speckle.Objects.BuiltElements | name, number, displayValue |
Level | Speckle.Objects.BuiltElements | name, elevation |
GridLine | Speckle.Objects.BuiltElements | baseCurve, label |
Structural
| Class | Namespace |
|---|
Node | Speckle.Objects.Structural.Geometry |
Element1D | Speckle.Objects.Structural.Geometry |
Element2D | Speckle.Objects.Structural.Geometry |
Element3D | Speckle.Objects.Structural.Geometry |
Property1D | Speckle.Objects.Structural.Properties |
Property2D | Speckle.Objects.Structural.Properties |
Material | Speckle.Objects.Structural.Materials |
Every class extends Base and defines typed properties with serialization attributes.
IL Repack: Dependency Isolation
The Problem
Host applications (Revit, Rhino, Grasshopper, Blender) load their own dependencies at specific versions. Without isolation, Speckle's dependencies (Newtonsoft.Json, System.Text.Json, etc.) conflict with the host's versions, causing runtime crashes.
The Solution
Speckle.Sdk.Dependencies uses IL Repack to merge and internalize all external dependencies into a single assembly with internalized types. This prevents namespace collisions entirely.
When to Use IL Repack
| Scenario | Use IL Repack? |
|---|
| Standalone .NET application | NO — no host dependency conflicts |
| Revit add-in / connector | YES — Revit loads its own Newtonsoft.Json |
| Rhino plugin / Grasshopper component | YES — Rhino has its own dependency set |
| Blender add-on (via .NET interop) | YES — isolation prevents conflicts |
| Unit test project | NO — test runners handle dependencies |
| Speckle Automate function | NO — runs in isolated container |
| Console tool / CLI | NO — no host application |
How It Works
Speckle.Sdk.Dependencies is added as a NuGet reference
- At build time, IL Repack merges external DLLs into the Speckle assembly
- All merged types become
internal, preventing namespace collisions
- The connector DLL ships as a self-contained unit
Dependency Injection Patterns
Registering Speckle Services
services.AddSingleton<Account>(provider =>
{
var account = new Account
{
token = Environment.GetEnvironmentVariable("SPECKLE_TOKEN"),
serverInfo = new ServerInfo
{
url = "https://app.speckle.systems/"
}
};
return account;
});
services.AddTransient<Client>(provider =>
{
var account = provider.GetRequiredService<Account>();
return new Client(account);
});
Transport as Transient
ALWAYS register transports as transient — each operation needs its own transport instance:
services.AddTransient<ServerTransport>(provider =>
{
var account = provider.GetRequiredService<Account>();
return new ServerTransport(account, streamId);
});
Progress Reporting
var progress = new Progress<ProgressArgs>(args =>
{
Console.WriteLine($"{args.ProgressEvent}: {args.Count}");
});
await Operations.Send(data, transports, onProgressAction: progress);
await Operations.Receive(objectId, transport, onProgressAction: progress);
ALWAYS provide progress reporting in interactive applications to give users feedback on long-running operations.
Reference Links
Official Sources