Projects are the top-level containers for your data:
Copy
from specklepy.core.api.inputs.project_inputs import ProjectCreateInput# Create a new projectproject = client.project.create(ProjectCreateInput( name="My First Speckle Project", description="Learning specklepy"))print(f"✓ Created project: {project.id}")# Get the project detailsproject = client.project.get(project.id)print(f"Project name: {project.name}")
If the workspace plan is already at your maximum project limit, you’ll need to delete some projects before creating a new one.
Projects replace what used to be called “Streams” in prior versions of Speckle.
Important: Sending geometry primitives (Point, Line, Mesh) directly will NOT make them visible in the 3D viewer! You must wrap them in a container object. This is why we use Base() below to group the geometry.
Copy
from specklepy.objects.geometry import Point, Line, Polylinefrom specklepy.objects import Base# Create some pointsp1 = Point(x=0, y=0, z=0)p2 = Point(x=10, y=0, z=0)p3 = Point(x=10, y=10, z=0)p4 = Point(x=0, y=10, z=0)# Create a lineline = Line(start=p1, end=p2)# Create a polyline (closed rectangle)# Polyline uses a flat list of coordinates: [x1, y1, z1, x2, y2, z2, ...]coords = [ p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, p3.x, p3.y, p3.z, p4.x, p4.y, p4.z, p1.x, p1.y, p1.z, # Close the shape]polyline = Polyline(value=coords)polyline.units = "m"# ✅ IMPORTANT: Wrap geometry in Base object for viewer visibilityobject = Base()object.line = lineobject.rectangle = polylineobject.points = [p1, p2, p3, p4]print("✓ Created geometry objects")
The Base class is the foundation of all Speckle objects. You can attach any properties to it dynamically! Wrapping geometry in Base ensures it’s visible in the 3D viewer.
from specklepy.api import operationsfrom specklepy.transports.server import ServerTransport# Create a transport (the vehicle for sending data)transport = ServerTransport(stream_id=project_id, client=client)# Send the data and the operation returns the object IDobject_id = operations.send(base=object, transports=[transport])print(f"✓ Sent data! Object ID: {object_id}")
The object_id is a unique hash that represents your data. You’ll use this to create a version. The hash is of the serialized data, not the object itself. Learn more about serialization.
Transports continue to use the v2 nomenclature of stream_id in place of project_id.
This will likely be deprecated in the future.
from specklepy.core.api.inputs.version_inputs import CreateVersionInput# Create a new modelmodel_input = CreateModelInput( project_id=project.id, name="My first model", description="This is my first model")model = client.model.create(model_input)# Create a versionversion_input = CreateVersionInput( project_id=project.id, model_id=model.id, object_id=object_id, message="My first version!")version = client.version.create(version_input)print(f"✓ Created version: {version.id}")print(f"View it: https://app.speckle.systems/projects/{project_id}/models/{model_id}")
# get the version and extract object idversion = client.version.get(project.id, version.id)root_object_id = version.referencedObject# Receive the data using the object_idreceived_data = operations.receive( obj_id=object_id, remote_transport=transport)print(f"✓ Received data!")print(f"Line start: ({received_data.line.start.x}, {received_data.line.start.y}, {received_data.line.start.z})")print(f"Rectangle points: {len(received_data.rectangle.points)}")print(f"Number of points: {len(received_data.points)}")
Why two steps?Versions store metadata (author, timestamp, message) separately from the actual
object data. This keeps version lists fast and lightweight. You only download
the full data when you explicitly receive() it.
You’ve now completed your first full Speckle workflow with Python! 🎉