Send and Send2 cannot hold the full graph in memory. SendPipeline streams to a local pack, uploads once, and tracks progress through client.Ingestion.
When to read this: production desktop connectors only — scripts use Automate with scripts. Read next: Load and publish model data Path E.
When to Skip This Page
| You are… | Use instead |
|---|---|
| Writing a script, notebook, or Grasshopper C# component | Automate with scripts with Send2/Receive2 |
| Following the Quickstart or sending a small graph | Send/Receive or Send2 — see Load and publish model data |
| Building a production desktop connector for a host app | This page |
Prerequisites
- Speckle Server version 3.0.3 or later (
client.Server.IsWorkspaceEnabled()doesn’t gate this). On server 3.0.11+, callclient.Model.CanCreateModelIngestion(project.id, model.id)to verify availability; on 3.0.3–3.0.10, confirm with your server admin. - Authentication and a project and model already created
- Familiarity with Load and publish model data
Detecting whether a server supports model ingestion at all, and telling that apart from a user only lacking permission, is exactly the pattern covered in Handling server capabilities — read it before writing the availability check below for anything beyond a one-off script.
Complete Example
This example follows the streaming pattern used by every Speckle desktop connector — see speckle-sharp-connectors,
RhinoContinuousTraversalBuilder and SendOperation.SendViaPackfile, for a full production implementation.Step-by-Step Explanation
1. Create the Ingestion Job
An ingestion is the server-side tracking record for this upload:SourceDataInput records which application and version produced this data — useful for diagnostics on models that fail to process.
2. Process Objects One at a Time, Not the Whole Graph
ISendPipelineFactory.CreateInstance builds a pipeline bound to this specific ingestion:
Process returns immediately once the object is written to the local temp file — it does not touch the network. WaitForUpload is what actually uploads: it finalizes the local file, uploads it to storage, and tells the server to start processing it. Always call both, and always call WaitForUpload even if you don’t care about the return value — skipping it means nothing is ever sent. SendPipeline implements IDisposable — always wrap it in a using.
uploadProgress is a required IProgress<StreamProgressArgs> — pass new Progress<StreamProgressArgs>() if you don’t need to report progress to a UI, matching the pattern used by Send/Send2.
3. Finding Out When the Version Is Ready
UnlikeSend2 + Version.Create, this pathway doesn’t hand you a version id synchronously. WaitForUpload only confirms the upload reached the server and processing has started — the server decompresses the pack, materializes the objects, and creates the version asynchronously, after your call returns.
Production connectors do not call
client.Ingestion.Complete after WaitForUpload on this pathway — the server completes the ingestion and creates the version itself once processing finishes. Calling Complete is for a different pattern: tracking a classic Send2 call with an ingestion record you complete manually (see ModelIngestionResource). Mixing the two — sending via SendPipeline and then also calling Complete — is redundant and not how any current Speckle connector uses this API.versionId off the success status:
client.Ingestion.Get(ingestion.id, project.id) instead and inspect its status.
If anything goes wrong on the client before WaitForUpload returns, report the failure so the ingestion doesn’t stay stuck in processing. ModelIngestionFailedInput.FromException builds the input from a caught exception:
FailWithError for unexpected/infrastructure failures, FailWithInvalid when the input itself was invalid (unsupported file, bad settings), and FailWithCancel only when the user explicitly cancelled. See ModelIngestionResource for the full state machine.
Cooperative Cancellation
If your connector supports cancelling an in-progress send, don’t cancel the pipeline unilaterally — request cancellation, then let the sending client observe and report it:RequestCancellation does not stop the pipeline by itself — it’s a signal the actively-sending process must poll or subscribe to and act on.
Common Pitfalls
Ingestion stuck in 'processing' forever
Ingestion stuck in 'processing' forever
On the client side, you only need to reach a terminal call if something goes wrong before or during upload:
FailWithError, FailWithInvalid, or FailWithCancel. If WaitForUpload succeeds, the server takes over and drives the ingestion to success or failed itself — you don’t call Complete. If an ingestion is stuck in processing with no client-side error, that’s a server-side processing problem (corrupt pack, unsupported data), not a missing client call — inspect it with client.Ingestion.Get.Calling client.Ingestion.Complete after WaitForUpload
Calling client.Ingestion.Complete after WaitForUpload
This is unnecessary for the
SendPipeline pathway and isn’t how any current Speckle connector uses this API — see Finding Out When the Version Is Ready above. Complete exists for a different, manual ingestion-tracking pattern built around Send2.Building the whole model in memory before calling Process() once
Building the whole model in memory before calling Process() once
Process does correctly serialize and write an entire nested object graph — including detached children — from a single call at the root. The reason to avoid this pattern at connector scale isn’t correctness, it’s memory: converting every element of a large host-application model before calling Process means holding the whole converted graph in memory at once. Convert and Process bottom-up instead, replacing each processed child with its ObjectReference in the parent, so converted objects can be released from memory as soon as they’re written to disk.Object IDs don't match IDs from Operations.Send
Object IDs don't match IDs from Operations.Send
SendPipeline uses a different serializer than Send/Send2. Don’t compare or reuse IDs across the two pathways — see Load and publish model data.404 or unsupported errors calling client.Ingestion methods
404 or unsupported errors calling client.Ingestion methods
The Model Ingestion API requires server version 3.0.3 or later. On 3.0.11+, use
client.Model.CanCreateModelIngestion(project.id, model.id) to verify availability before enabling this pathway; on 3.0.3–3.0.10, confirm compatibility with your server admin. Don’t wrap the whole pathway in a broad try/catch to detect this — see Handling server capabilities for the narrow-catch pattern that tells “server too old” apart from “not authorized.”Next Steps
Load and publish model data
Path E decision guide
Handling server capabilities
Permission and version checks
ModelIngestionResource
Ingestion lifecycle API