Agent? Fetch sharkmagic.si/docs/llms.txt or this page as markdown
Quickstart#
From zero to a command running in a Space, then a fork of it. Two paths, each under 10 minutes: the CLI and the SDK (Python or TypeScript). Both talk to the same API.
0. What you need#
- An account API key. Shark is in early access: your invite contains an account key (
shk_live_…). Request access at sharkmagic.si. Once you are logged in,shark keys create --name cimakes more (cli.md). - Keep the key in the environment or in the CLI's credentials file. Never paste it into code, into a file of the Space, or into an agent's prompt.
export SHARK_API_KEY=... # from your invite; the CLI can store it for you instead (step A1)
That is the whole configuration. The key belongs to the account service (https://api.sharkmagic.si, the
default): the CLI and the SDKs exchange it there for short-lived node tokens and send each call to the node that holds
your Space (the hosted node (https://hel1.sharkmagic.si)). The key itself never goes to a node.
Do not set SHARK_API. SHARK_API (or shark login --api …) puts the CLI and the SDKs in direct mode: the key
goes to that one node as is, and only a self-hosted node's own keys work there. An account key in direct mode answers
401 unauthenticated. If you set it earlier: unset SHARK_API, then shark login --api-key - again.
Installing.
-
CLI (Linux and macOS, amd64 and arm64; one static file):
curl -fsSL https://dl.sharkmagic.si/install.sh | shThe script picks the build for your OS and CPU, checks its sha256 against the release's
SHA256SUMSand refuses on any mismatch, then installssharkinto~/.local/bin(/usr/local/binas root;SHARK_INSTALL_DIR=…to choose,SHARK_VERSION=0.2.0to pin). Prefer to look first: downloadinstall.shfrom the same page, read it, run it. Or take the binary for your platform andSHA256SUMSfrom the download page and check it withsha256sum -c --ignore-missing SHA256SUMS. Later:shark updatesays whether a newer release exists;shark update --installreplaces the binary (same checksum check). -
SDKs: not on PyPI/npm yet (the package names are being decided); your invite links the build. The commands below use these placeholders:
| placeholder | what it installs | provisional name |
|---|---|---|
<PY_SDK_INSTALL> |
the Python SDK (3.9+, no dependencies) | pip install shark-sdk, imported as shark |
<TS_SDK_INSTALL> |
the TypeScript SDK (Node 22.18+, no dependencies; ESM with types) | npm install @sharkmagic/sdk |
From a checkout of the source, the same things are go build -o ~/bin/shark ./cmd/shark,
pip install ./sdk/python and npm install ./sdk/typescript (its prepack builds dist/).
Path A: the CLI#
A1. Install and log in (1 min)#
curl -fsSL https://dl.sharkmagic.si/install.sh | sh
shark login --api-key - # paste the key at the prompt; it never enters argv or history
Expected:
Logged in with an API key: tenant <your-account>. Credentials in /home/you/.shark/credentials (0600).
From a script or CI, pipe it (shark login --api-key - < keyfile) or just export SHARK_API_KEY. At a terminal,
shark login alone logs you in through the browser with a one-time code instead (a login of yours, not a key).
Check it:
shark whoami
tenant <your-account> (<your-workspace>), api_key key:…, scopes [exec files:read files:write spaces:read spaces:write usage:read]
control plane https://api.sharkmagic.si
Every account starts with a personal workspace (just you; nothing to set up). Teams are opt-in:
shark org create, shark org invite (cli.md).
A2. Create a Space (30 s)#
shark create --name demo
tti-ec8e46415dcc2dc3182daae2bc8a8aa2
The id is <prefix>-<32 hex>; every command below accepts the id or the name (demo).
A3. Run commands (1 min)#
Quote the whole command line: shark run sends it to bash in the Space.
shark run demo -- 'echo hello > hello.txt && cat hello.txt && uname -s'
hello
Linux
That command ran in a fresh microVM that no longer exists; hello.txt was committed to the Space. The next command
sees it, and shark run exits with the command's own exit code:
shark run demo -- 'ls && exit 3'; echo "exit code: $?"
hello.txt
exit code: 3
Output streams while the command runs. If your connection drops, shark run reconnects and resumes the output; the
command never runs twice (each call has an op_id). Ctrl-C stops the command, not just the CLI.
A4. Fork it (30 s)#
shark fork demo -n 2
tti-376de78ec13b0d33216c9abf966d6267
tti-9909f1e3035a270e6baeb024b6f54f87
Each fork is a new Space at the same commit, with nothing copied. Run something in one:
shark run tti-376de78ec13b0d33216c9abf966d6267 -- 'cat hello.txt'
hello
A5. Move files in and out (1 min)#
shark cp -r ./my-project demo:my-project # upload a directory (one commit)
shark cp demo:hello.txt - # print a file; reads never start a VM
shark files ls demo
A6. Exposed server and preview#
Live on the hosted node: the server runs in its own microVM, private by default, sleeps when idle and wakes on the next request (concepts.md). It is billed while awake, by size: remove it when you are done.
shark expose demo 3000 --cwd app -- node server.js # a server that sleeps when idle and wakes on request
shark open demo 3000 # one-time login link (60 s) to https://<id>-3000.preview.sharkmagic.si
shark expose demo --logs 3000 # its output
shark expose demo --rm 3000 # stop it and revoke the preview cookies
shark connect demo (an interactive shell where each line runs as one command and cd/export carry over) is
coming: it passed its local gates but is not yet verified on the hosted node.
A7. Clean up#
shark ls
shark rm demo
Path B: the SDK#
B1. Install (1 min)#
<PY_SDK_INSTALL> # Python
<TS_SDK_INSTALL> # TypeScript
Both read SHARK_API_KEY from the environment (Python also ~/.shark/credentials, written by shark login) and go through
the account service for you, like the CLI. Leave SHARK_API unset (direct mode is for self-hosted nodes).
B2. Python: create, run, read a file, fork (3 min)#
from shark import Shark
shark = Shark() # SHARK_API_KEY
space = shark.spaces.create(name="quickstart")
print(space.id, space.state)
r = space.run("echo hello > hello.txt && cat hello.txt")
print(r.exit, r.stdout.strip(), r.shark_status)
print(space.files.read_text("hello.txt").strip()) # served from the committed tree, no VM
for fork in space.fork(count=2):
print(fork.id, fork.run("cat hello.txt").stdout.strip())
print("exit", space.run("exit 7").exit) # a failing command is a result, not an exception
Expected (ids differ):
tti-a47ffe076f139d770d99dce9a6db0ffe ready
0 hello ok
hello
tti-e0b78dd7df202284936c5803a9cdb88a hello
tti-79083d0f699a6b800adba0a1742329d3 hello
exit 7
Useful next calls: space.run(cmd, cwd=..., env=..., timeout=..., check=True), space.stream(cmd) (live events),
space.start(cmd) (returns an Op at once), space.files.write(...), space.files.upload_dir(...),
space.hibernate(), space.delete(). Async: AsyncShark.
B3. TypeScript: the same (3 min)#
import { Shark } from "@sharkmagic/sdk";
const shark = new Shark(); // SHARK_API_KEY
const space = await shark.spaces.create({ name: "quickstart-ts" });
console.log(space.id);
const r = await space.run("echo hello > hello.txt && cat hello.txt");
console.log(r.exit, r.stdout.trim(), r.sharkStatus);
const [fork] = await space.fork({ count: 1 });
console.log(fork.id, (await fork.run("cat hello.txt")).stdout.trim());
Expected:
tti-8d5d6fa7ea7287b3cc76d720830cbb92
0 hello ok
tti-d795718d53c4d099f95ada76cdac1e87 hello
B3b. What an SDK Space gets (1 min)#
Spaces made by the SDK are sdk Spaces: your account's secrets, your GitHub connection and background jobs do not reach them unless you ask, per Space (a platform's end users never see your keys):
sp = shark.spaces.create(labels={"customer": "acme"}, egress="coding") # nothing of the account
sp.secrets.set("STRIPE_KEY", key, hosts=["api.stripe.com"]) # this Space's own secret (inject)
sp.update(account_secrets=["OPENAI_API_KEY"], github=True) # opt in: one account secret, GitHub
sp.run("python worker.py &", background=True) # keep what it leaves running
for s in shark.spaces.list(label="customer:acme"): print(s.id)
const sp = await shark.spaces.create({ labels: { customer: "acme" }, egress: "coding" });
await sp.secrets.set("STRIPE_KEY", key, { hosts: ["api.stripe.com"] });
await sp.update({ accountSecrets: ["OPENAI_API_KEY"], github: true });
profile: "devbox" (profile="devbox") makes one like shark create does. Every field:
api.md.
B4. Clean up#
for s in shark.spaces.list(name="quickstart"):
s.delete()
Path C: raw HTTP (for any language)#
An account key goes only to the account service. Exchange it there for a node token (5 minutes; ask for a new one before it expires); the answer also names the node to call:
curl -sS -X POST https://api.sharkmagic.si/v1/token -H "Authorization: Bearer $SHARK_API_KEY" \
-H 'Content-Type: application/json' -d '{}'
# {"token":"…","token_type":"Bearer","expires_in":300,"tenant":"…","scopes":[…],"space":null,"node":{"id":"hel1","url":"https://hel1.sharkmagic.si"}}
export SHARK_NODE=https://hel1.sharkmagic.si # node.url from the answer
export SHARK_TOKEN=… # token from the answer (never log it)
Then call the node with the token:
curl -sS -X POST "$SHARK_NODE/v1/spaces" -H "Authorization: Bearer $SHARK_TOKEN" \
-H 'Content-Type: application/json' -H 'Idempotency-Key: qs-create-1' -d '{"name":"curl-demo"}'
# {"id":"tti-…","state":"ready","name":"curl-demo",…}
curl -sS -X POST "$SHARK_NODE/v1/spaces/curl-demo/exec" -H "Authorization: Bearer $SHARK_TOKEN" \
-H 'Content-Type: application/json' -d '{"cmd":"echo hello","op_id":"qs-1"}'
# {"exit":0,"shark_status":"ok","stdout":"hello\n","stderr":"","op_id":"qs-1","replayed":false,…}
curl -sS -X POST "$SHARK_NODE/v1/spaces/curl-demo/fork" -H "Authorization: Bearer $SHARK_TOKEN" \
-H 'Content-Type: application/json' -H 'Idempotency-Key: qs-fork-1' -d '{"count":1}'
# {"spaces":[{"id":"tti-…","fork_of":"tti-…","fork":{"bytes_copied":0,…},…}]}
For an existing Space, {"space": "<id>"} in the token request returns the node that holds it. The SDKs and the CLI do all of this (tokens, refresh, placement) for you.
Send the same exec again with "op_id":"qs-1": you get the stored result with "replayed": true, and the command
does not run a second time. Every endpoint: api.md.
If something fails#
| you see | it means | do |
|---|---|---|
not logged in / 401 unauthenticated |
no key, a wrong or revoked one, or SHARK_API is set (direct mode: account keys do not work there) |
unset SHARK_API, then shark login --api-key -, or check SHARK_API_KEY |
404 not_found on your Space |
wrong id or name, or it belongs to another account | shark ls |
429 lease_cap_reached |
your account already runs the maximum commands at once | wait Retry-After seconds; the CLI and SDKs do it for you |
429 quota_exceeded |
an account cap: Spaces held (details.quota: spaces) or awake at once (awake) |
shark quotas; delete a Space, or shark hibernate one you are not using |
402 credits_exhausted |
balance at zero | top up; files stay readable |
422 unsupported |
the route exists in the spec but not on this node yet | see the page for that feature |
All codes: errors.md. Next: concepts.md, then point your agent at Shark with agents.md and the AGENTS.md snippet.
<sub>Expected outputs were captured on 2026-11-05 against the real sharkfs api and these exact CLI and SDK builds, on a
local server whose commands ran without a VM (so no timings are shown here); the login and whoami lines show the
CLI's format with an account key (updated 2026-11-07). Account keys working on the hosted node through the account
service (key → node token → command → metered usage → balance → revocation) was checked end to end on 2026-11-07.
Measured timings on metal: numbers.md.</sub>
