Locations
Give a folder in an existing bucket a name in your Project, and keep a versioned list of its files.
Most robotics data already sits in a bucket: thousands of MCAP logs and videos
under a prefix such as s3://example-robotics/recordings/. A Location gives
that folder a name in your Project, recordings, and keeps a table of its
files. Your team finds and reads the files through that name, Spiral never
copies the bytes, and a pinned commit always shows the same file list.
HTTP API
The HTTP API is available under /api/locations. Requests use camelCase fields and
tagged unions with matching discriminator and payload names. Counts and timestamps
that use 64-bit integers are returned as decimal strings. Existing Rust and CLI
clients continue to use the established endpoints.
An example
After a refresh, the Location recordings holds a file inventory like this:
path | size | last_modified | etag |
|---|---|---|---|
2026-10-12/run-0041.mcap | 2.1 GB | 2026-10-12 18:02:11 | 9b2cf5… |
2026-10-12/run-0041-front.mp4 | 640 MB | 2026-10-12 18:02:40 | 41d0a8… |
Each row also has the file's uri, which a reader uses to fetch the bytes. In
Studio, the Preview tab of the Location shows the inventory and previews
images, video, and text in place.
Locations are separate from Project storage. Project storage holds the data that Spiral manages, such as Tables. A Location names data that already exists somewhere else, and only reads it.
How readers reach the bucket
A Location records a storage root URI, such as
s3://example-robotics/recordings/, and the
Connection that authorizes reads of it. The
Connection holds a key, or an identity that your IAM role trusts, and the root
lies inside the Connection URI. A reader with access to the Location gets a
short-lived, read-only credential for that root and reads the bucket directly.
Connect Amazon S3,
Cloudflare R2,
Ceph, or
Hugging Face.
Spiral reads no storage without credentials, so a public bucket or repository also needs a Connection.
Existing Location versions without a Connection remain stored, along with their history. Reading or using one returns an error identifying the version and its missing Connection. Create a Location with a Connection to read that root. A connected version remains usable even if its history includes a version without a Connection.
Every Location needs Project storage for its inventory. The inventory stores object metadata only, never copies of the bytes.
The file inventory
A new Location has an empty inventory. A refresh lists the storage root and reconciles the inventory: it adds new paths, removes missing ones, and updates a path whose URI, size, modification time, or ETag changed. It then publishes the inventory and a new Location version in one commit, and records how many rows changed. Zero means that the bucket matched the inventory.
Because a commit selects both the Location version and its inventory, a pinned commit keeps its file list after later refreshes, and after a change of root. That makes a training run's input list reproducible.
The inventory records what the provider returned at the time of the refresh. A later read can return newer bytes, or fail if the object was deleted. The ETag is the provider's metadata; it is not checked when you read.
Each refresh adds small files to the inventory, and Table maintenance compacts them like those of any Table.
Where next
- Create a Location in Studio and publish its first inventory.
- Change the root when the data moves to a new prefix.
Availability
Reading a gs:// root through a
Google Cloud Storage
Connection is planned.
Reference
The Connection
A Location version stores its root URI and its Connection's Resource ID. Reading costs one credential exchange per hour, not one per object. A Location stores its Connection's Resource ID, not its name, so renaming the Connection breaks nothing. If the Connection is dropped, reads of the Location fail with an error that says who dropped it and when.
Object URIs
A Location returns the provider URI of each file:
s3://example-robotics/recordings/2026-10-12/run-0041.mcapAnyone whose client can reach the bucket can read this URI. For a private bucket behind a Connection, that means a reader to whom Spiral gave a credential. A reader who keeps the URI can still read the object after the Location is dropped, if their own access allows it.
A stored URI does not change when the Location root changes.
Permissions
| Operation | Permission |
|---|---|
| Create a Location | location:create |
| Name a Connection from a Location | connection:bind on the Connection |
| Revise the root URI or the Connection | location:update |
| Refresh the inventory | location:read, location:list, and location:update |
| Drop a Location | location:read and location:drop |
| Read the inventory | location:read; also location:list for a Connection Location |
The inventory is a Table, so it also needs Table permissions: table:create
and table:write to create, table:read to read, and table:write to
refresh, maintain, or drop. The standard viewer, editor, and admin roles can
read a Location.
For a URI Location, these permissions control who can find the Location and learn its root, not who can read the bucket. The bucket's own access rules decide that.
The Locations HTTP reference documents the resource operations.