# Limits

What the hosted node enforces at launch. **Your account's live values are authoritative**: `GET /v1/quotas` (your
quotas with current use) and `GET /v1/capabilities` (the node's limits and features, no auth). Do not hard-code the
numbers below in an agent; read them.

```sh
curl -sS https://hel1.sharkmagic.si/v1/capabilities
shark quotas --json      # or GET /v1/quotas on the node with a node token (api.md, "Auth")
```

Every limit below answers with a stable error code when you hit it ([errors.md](errors.md)); none is a silent cap.

## Your account

The account service sets per-account caps; the node enforces them and `GET /v1/quotas` shows each as a row with
`limit` and `used` (`scope: tenant`): `spaces` (held, awake or hibernated), `awake_spaces`, `concurrent_leases`. The
same answer names your account's class (`limits_class`) and how to raise a limit (`raise`). Defaults at launch, by
class (an account can be given others; your rows are the truth):

| cap | new account (first 7 days) | running on promotional credit only | standard | partner | when exceeded |
|---|---|---|---|---|---|
| Spaces held | 20 | 10 | 50 | 200 | `429 quota_exceeded`, `details.quota: spaces`: delete one |
| Spaces awake at once | 7 | 7 | 7 | 100 | `429 quota_exceeded`, `details.quota: awake` on create, fork or wake: hibernate one (`shark hibernate`, or `shark space set <space> --idle 30m` so idle ones sleep by themselves) |
| commands running at once | 4 | 2 | 6 | 12 | queued, then `429 lease_cap_reached` (retryable, the command did not start) |
| exposed processes awake at once | 2 | 1 | 3 | 20 | refused with `quota_exceeded`: stop or sleep one |
| CPU per command | 4 CPUs | 2 CPUs | the VM's 8 vCPU | the VM's 8 vCPU | none: the command runs slower |

A Space does not hibernate by itself unless it, or the node, has an idle setting: hibernate the ones you are not using.

Every `429 quota_exceeded` names the cap (`details.quota`, `limit`, `used`), your `details.limits_class` and
`details.raise`: how to get more (write to support@sharkmagic.si; design partners and SDK platforms get the
`partner` limits). Prices are the same in every class.

```sh
shark quotas --json      # {"limits_class": "partner", "raise": "...", "data": [{"name": "awake_spaces", "scope": "tenant", "limit": 100, "used": 12}, ...]}
curl -sS -H "Authorization: Bearer $SHARK_API_KEY" https://api.sharkmagic.si/v1/limits   # class, caps, label caps (no use)
```

### Partner limits

For design partners and SDK platforms whose own end users each get Spaces: the `partner` class, set by Shark (ask
us; an operator turns it on with one command and it reaches the nodes within seconds). Its numbers are sized to what
one node serves: 200 Spaces is the node's per-account ceiling, 100 awake is a quarter of the node's 400 live
Spaces. The node's own protections still apply on top of any class: 50 GiB of disk per account, and its admission
(`503 out_of_capacity` while memory or CPU is short, retryable).

### Per-label limits

A platform can cap each of its own end users by a label of their Spaces (Spaces take `labels` at create, fork and
`PATCH`). A selector `key` caps **each value** of that label on its own; `key=value` caps that one value and wins
over `key` for it. Two counts: `awake` (Spaces awake at once) and `spaces` (held). A Space without the label is not
capped by it. They only tighten: the account's caps above still apply.

```sh
shark quotas labels set end_user --awake 3 --spaces 20     # every end user: 3 awake, 20 held
shark quotas labels set end_user=acme --awake 10            # acme gets 10 awake
shark quotas labels --json                                  # {"class": ..., "label_caps": {"end_user": {"awake": 3, "spaces": 20}, ...}}
shark quotas labels rm end_user=acme
# HTTP (account service): PATCH /v1/limits/labels {"labels": {"end_user": {"awake": 3}}}  (null removes a selector)
#                         PUT   /v1/limits/labels {"labels": {...}}                       (replaces all; null clears)
```

| limit | value | when exceeded |
|---|---|---|
| selectors per account | 32; key `[A-Za-z0-9._/-]{1,63}`, value 1–255 chars | `400 invalid_request`, `details.field` |
| a value's Spaces awake / held | what you set (integer 0–1,000,000; 0 = none allowed) | `429 quota_exceeded`, `details.quota: label_awake` or `label_spaces`, `details.label` (`end_user=alice`), `details.selector`, `limit`, `used`; on create, fork, wake and on a `PATCH` that moves a Space into a full value |

`GET /v1/quotas` shows one row per selector and count (`scope: label`): for `key=value` its use; for `key` the
fullest value (`label`, `used`) and how many values you use (`values`). A change reaches the nodes within one poll of
the account service (about 2 s).

## Commands

| limit | value | when exceeded |
|---|---|---|
| lease shape (each command's microVM) | 8 vCPU, 4 GiB | — |
| `timeout_s`, sync `exec` (JSON when it ends) | default 120 s, **max 900 s** | `400 invalid_request`, `details.max_s: 900` |
| `timeout_s`, streaming (`Accept: text/event-stream`) or async (`"wait": false`) | default 30 min, **max 7,200 s (2 h)** | `400 invalid_request`, `details.max_s: 7200` |
| output returned by a sync `exec` | last 1 MiB of each stream (`stdout_truncated: true`) | the full output stays in the op log |
| op log (stored output per op) | 32 MiB per stream, then `truncated` | |
| op retention (result and output) | 7 days | `410 op_expired`; the commit stays in `audit` |
| `cmd` | 1 B – 256 KiB | `400` |
| `stdin` | 1 MiB | `413 payload_too_large` |
| `env` per request | 128 names, 16 KiB per value | `400` |
| `op_id` | 1–64 of `[A-Za-z0-9_.:-]` | `400 invalid_op_id` |
| commands running at once, per account | your account's cap ([above](#your-account); then a queue; too long a wait gives `429 lease_cap_reached` before the command starts) | `429`, retryable |
| node admission (memory and CPU) | the node refuses new commands, Spaces and wakes while its memory is short or its CPUs are saturated; live state: the `node_admission` row of `GET /v1/quotas` | `503 out_of_capacity` with `Retry-After` (`details.reason`), retryable; nothing started |
| `stdin` streaming into a running command (`stdin_stream`) | not served yet | `422 unsupported` |

## Spaces and files

| limit | value | when exceeded |
|---|---|---|
| Spaces per account (live and hibernated) | your account's cap ([above](#your-account)) | `429 quota_exceeded` |
| disk per account on the node | 50 GiB (51,200 MiB) | `507 insufficient_storage`, `scope: tenant` |
| live Spaces per node (hibernated ones do not count) | 400 | `429 space_limit_reached`, retryable |
| forks per call | 1–50 | `400` |
| labels per Space | 32; key `[A-Za-z0-9._/-]{1,63}`, value ≤ 255 chars | `400` |
| name | `[A-Za-z0-9._-]{1,63}`, unique per account | `400` / `409 conflict` |
| JSON request body | 1 MiB | `413` |
| single file upload (`PUT /v1/spaces/{id}/files`) | 512 MiB, streamed | `413` |
| archive upload (`POST /v1/spaces/{id}/files/archive`) | 2 GiB | `413` |
| `Idempotency-Key` | 1–255 chars, kept 24 h | |

## Secrets, network, terminals

| limit | value |
|---|---|
| secrets per Space | 64 |
| secret value size | 16 KiB |
| egress `allow` entries | 256 |
| terminals per Space (`shark connect`) | 8 (`429 too_many_terminals`) |
| one-time preview login link | 60 s, one use |
| exposed process idle before it sleeps | `idle_s`, default 30 s |

## API

| limit | value |
|---|---|
| request rate per credential | 100/s, burst 500 (`429 rate_limited` with `Retry-After`) |
| failed authentications per address | limited (`429 auth_rate_limited`) |
| transport | HTTPS only (plain HTTP is refused except on loopback) |

## Not available (today)

GPUs; non-Linux guests; process memory carried across commands; the services, snapshots, restore, connectors,
model-gateway, events and webhooks routes (`422 unsupported`); `stdin` streaming; E2B SDK compatibility; a published
SLA or uptime record. `shark connect` is **coming** on the hosted node (exposed processes and preview URLs are live:
[concepts.md](concepts.md#exposed-processes-and-preview-urls)).

<sub>Values: `cmd/sharkfs` (`api_v1*.go`, `node*.go`), `internal/apiv1`, `internal/secrets`, and the hosted node's
configuration (`deploy/v1/shark-api.service`) as of 2026-11-05; the account defaults from the account service
(`control-plane/src/config.ts`, `DEFAULT_CAPS`) as of 2026-11-07. Account quotas can differ per account; `GET
/v1/quotas` tells you yours.</sub>