Skip to main content

Goal

Resolve proxies at the root of connector-published data to group elements by level, material, or instance — common when reporting on Revit or Rhino connector output.

When to use this

After Receive2 on connector data, when you need elements grouped by level or category via proxy collections rather than tree hierarchy alone.
  1. Flatten() and index by applicationId — see Find objects by property
  2. Read proxy collections from the root (groupProxies, colorProxies, etc.)
  3. Resolve each proxy’s objects list against the index
As a producer: you typically do not create proxies from scripts — send DataObject graphs and let connectors produce proxies.

Prerequisites

Proxy Types in Speckle.Sdk

Proxy classes implement IProxyCollection, which requires an objects list of source application IDs:
https://mintcdn.com/speckle/VtRPWzmN-ULoLfIv/images/developers/sdks/csharp.svg?fit=max&auto=format&n=VtRPWzmN-ULoLfIv&q=85&s=f2970e3b8bd4a2c7899b46211a42f251Example
using Speckle.Sdk.Models.Proxies;

public interface IProxyCollection : ISpeckleObject
{
    public List<string> objects { get; set; }
}

var groupProxy = new GroupProxy
{
    name = "Exterior Walls",
    objects = ["wall-app-id-1", "wall-app-id-2"]
};

var colorProxy = new ColorProxy
{
    name = "Concrete",
    value = unchecked((int)0xFF808080),
    objects = ["wall-app-id-1", "column-app-id-1"]
};
Additional proxy types (materials, levels, instance definitions) follow the same pattern. Exact shapes: Proxy Schemas.
As a producer: you typically do not create proxies when publishing from a script or service — send your Base/DataObject graph and let connectors produce proxy collections from host-app organization.

Complete Example

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.Sdk.Models;
using Speckle.Sdk.Models.Extensions;
using Speckle.Sdk.Models.Proxies;

var byAppId = receivedData.Flatten()
    .Where(o => o.applicationId is not null)
    .ToDictionary(o => o.applicationId!);

if (receivedData["groupProxies"] is IEnumerable<object> groupProxies)
{
    foreach (var proxy in groupProxies.OfType<GroupProxy>())
    {
        var members = proxy.objects
            .Select(id => byAppId.GetValueOrDefault(id))
            .Where(o => o is not null);

        Console.WriteLine($"Group '{proxy.name}': {members.Count()} objects");
    }
}

Step-by-Step Explanation

Resolving Simple Proxies (Group, Color)

Every built-in proxy type implements IProxyCollection. Resolution is three steps: index by applicationId, look up each id in the proxy’s objects list, pair resolved objects with proxy metadata.
https://mintcdn.com/speckle/VtRPWzmN-ULoLfIv/images/developers/sdks/csharp.svg?fit=max&auto=format&n=VtRPWzmN-ULoLfIv&q=85&s=f2970e3b8bd4a2c7899b46211a42f251Example
Dictionary<string, List<Base>> ResolveProxies<T>(
    IEnumerable<T> proxies,
    Dictionary<string, Base> byAppId
) where T : IProxyCollection
{
    var result = new Dictionary<string, List<Base>>();

    foreach (var proxy in proxies)
    {
        var resolved = proxy.objects
            .Select(id => byAppId.GetValueOrDefault(id))
            .OfType<Base>()
            .ToList();

        result[string.Join(",", proxy.objects)] = resolved;
    }

    return result;
}
Not every applicationId in a proxy’s objects list is guaranteed to resolve. Always use GetValueOrDefault, never a direct indexer that throws.

Resolving Instance Definitions

Instances are two-hop: an InstanceProxy references an InstanceDefinitionProxy by definitionId, and the definition’s objects list holds geometry application IDs.
https://mintcdn.com/speckle/VtRPWzmN-ULoLfIv/images/developers/sdks/csharp.svg?fit=max&auto=format&n=VtRPWzmN-ULoLfIv&q=85&s=f2970e3b8bd4a2c7899b46211a42f251Example
using Speckle.Sdk.Models.Instances;

var definitionsById = receivedData.Flatten()
    .OfType<InstanceDefinitionProxy>()
    .ToDictionary(d => d.applicationId!);

var instances = receivedData.Flatten().OfType<InstanceProxy>();

foreach (var instance in instances)
{
    if (!definitionsById.TryGetValue(instance.definitionId, out var definition))
    {
        continue;
    }

    var geometry = definition.objects
        .Select(id => byAppId.GetValueOrDefault(id))
        .OfType<Base>()
        .ToList();

    Console.WriteLine(
        $"Instance of '{definition.name}': {geometry.Count} objects, transform: {instance.transform}"
    );
}
instance.transform is a Speckle.DoubleNumerics.Matrix4x4 — apply it to place each instance correctly.
Prefer root-level property lookup (receivedData["instanceDefinitionProxies"]) when you know the sender’s convention; fall back to Flatten().OfType<...>() when you don’t.

Common Pitfalls

Root-level property names (groupProxies, colorProxies, etc.) are a sender convention. Check Proxy Schemas or the sending connector’s documentation.
InstanceProxy.definitionId matches an InstanceDefinitionProxy.applicationId, not id. Index definitions by applicationId.
Build it once per received graph — see Find objects by property.
A definition’s objects list can contain another InstanceProxy. Resolve and compose transforms recursively, multiplying matrices from outermost to innermost.

Next Steps

Objects and Traversal

Base graph structure proxies attach to

Find objects by property

applicationId index pattern

Client API

Receive connector data via IClient
Last modified on July 10, 2026