Connect

SDKs

TypeScript, Python, Go and Rust clients that pass one shared test suite. Paths instead of ids, streaming uploads and ranged downloads, safe writes, edits, sidecars, search, changes, webhooks and actions.

LanguagePackageFolderRuns on
TypeScript / JavaScript@sidespace/sdksdks/typescriptNode 18+, Deno, Bun, browsers (anything with fetch)
Pythonsidespacesdks/pythonPython 3.9+, standard library only
Gogithub.com/blendduck/sidespace/sdks/gosdks/goGo 1.22+, standard library only
Rustsidespacesdks/rustAsync, on reqwest (rustls) and tokio

All four are Apache-2.0 and cover the same ground for files. They are not on npm, PyPI, the Go module proxy or crates.io yet: use them from a checkout of the repository, or copy the folder into your project.

npm install ./sdks/typescript

Set up

import { SideSpace } from "@sidespace/sdk";

const side = new SideSpace({ baseUrl: "https://side.example.com", token: process.env.SIDESPACE_TOKEN! });

Spaces can be given by name or id, and files by path. Errors carry the server's status, code and message: SideSpaceError in TypeScript and Python, and in Go and Rust an error with IsPreconditionFailed / is_precondition_failed() and the current version for a safe write that lost. Each folder's README has the full method list.

What's covered

AreaTypeScriptPython
Spaces and foldersspaces(), space(), node(), list(), mkdir()spaces(), space(), node(), list(), mkdir()
Upload and downloadupload(), download(), open() with rangeupload() (streams file objects), download(), open() with range
Versionsversions(), restoreVersion()versions(), restore_version()
Part of a fileedit(), appendText()edit(), append_text()
The pastasOf on node(), list(), open(), download(); rewind()as_of= on node(), list(), open(); rewind()
SidecarsgetSidecar(), putSidecar()get_sidecar(), put_sidecar()
Searchsearch(q, { space, type, mode })search(q, space=, type=, mode=)
Changesjournal(), changes() (async iterator)journal(), changes() (generator)
WebhookscreateWebhook(), verifyWebhookSignature() from @sidespace/sdk/webhookscreate_webhook(), verify_webhook_signature()
Actions and pipelinesactions(), run(), startRun(), waitRun(), runs(), createPipeline()actions(), run(), start_run(), wait_run(), runs(), create_pipeline()
Commentscomments(), addComment(), editComment(), resolveComment()comments(), add_comment(), edit_comment(), resolve_comment()
Forksfork(), forks(), forkChanges(), applyFork()fork(), forks(), fork_changes(), apply_fork()
PluginsinstallPlugin(), PluginRuninstall_plugin(), PluginRun

Go and Rust cover the same file operations (spaces, paths, streaming upload and ranged download, safe writes, byte-range edits, versions, sidecars, search, comments, changes, actions and webhooks), with Go and Rust names (UploadText, upload_text).

Anything else in the API reference is one request() away (Do in Go):

const film = await side.space("Film A");
const presence = await side.request("GET", `/v1/spaces/${film.id}/presence`);

One test suite for all four

sdks/conformance/cases.json is one list of 34 steps (save, read ranges, safe-write conflicts, edits, restore, folders, sidecars, comments, search and errors) that every SDK runs against a live server, each through its own runner. check.py compares their answers, so the four SDKs can't drift apart. All four pass. To check one yourself against a dev server:

Shell
SIDESPACE_SERVER=… SIDE_TOKEN=… SPACE=… python3 sdks/conformance/runner.py | python3 sdks/conformance/check.py --sdk python

A new SDK, in any language, is done when it passes every step.

A complete example

Score every clip in /dailies that hasn't been scored yet with your own model, reading only the first 8 MB of each, and keep the result in a sidecar:

import { SideSpace, SideSpaceError } from "@sidespace/sdk";

const side = new SideSpace({ baseUrl: process.env.SIDESPACE_URL!, token: process.env.SIDESPACE_TOKEN! });

for (const file of await side.list("Film A", "/dailies")) {
  if (!file.mime?.startsWith("video/")) continue;
  const path = `/dailies/${file.name}`;
  const done = await side.getSidecar("Film A", path, "x-mymodel").catch((e) => {
    if (e instanceof SideSpaceError && e.status === 404) return null;
    throw e;
  });
  if (done) continue;
  const head = await side.open("Film A", path, { range: [0, 8 * 1024 * 1024 - 1] });
  const score = await myModel(await head.arrayBuffer());
  await side.putSidecar("Film A", path, "x-mymodel", { score, model: "mymodel-2" });
}

To keep up with new clips as they land, run the same code from a webhook, or package it as a plugin and add a pipeline: both keep their place and retry for you.