# 3pm-backend route `getBlob`

Paths are relative to the backend's origin (https://fleet.edgecr4ft.net, or your own 3pm-backend), not to this site. Index: https://3pm.edgecr4ft.net/docs/backend/api.md

---

tokens:
  claude: 986
  o200k: 632
# GET /api/buckets/{clusterId}/blobs/{blobId} (getBlob)

A call's bytes as they arrive: the headers come back before the first byte, and `Range` works once the blob is complete. A node reading a blob of its own call needs no scope: the blob is the authorisation, and one that is not its own is a 404. `?k=` (a `viewBlob` token) lets a browser tag read one, and `?as=mjpeg` renders a stream of JPEG messages for an `<img>`.

- auth: an account — Authorization: Bearer 3pmu_<token>, or the dashboard's session cookie
- node: also open to a node's own bearer (3pma_…) when its row is granted fleet:invoke; that node's bucket only, and for the calls it placed itself
- query: GetBlobQuery (URL parameters, each optional)
- answer: not JSON: a page, an asset or the client module

## Details

A `file` blob is bytes with a length: the headers carry neither `Content-Length` nor `Accept-Ranges` while it is open, both once it is complete, and `Range` works from then on. A `live` blob is a stream of messages (20-byte header, `KEY`/`END`/`INIT`/`DISCONT` flags, at most 1 MiB each) and has neither ever — it is followed, not fetched. A reader joins on the last message a decoder can start on, with the stream's `INIT` in front of it, and one that falls further behind than `--live-window-ms` is sent on to the newest `KEY`: it sees a gap in `seq`, and nothing is inserted to tell it so. `?live=1` says what the caller meant and changes nothing: the mode is the blob's, and a stream whose window is gone is a 404 like any blob that is not there. `?as=mjpeg` serves the same stream as `multipart/x-mixed-replace` with a fresh 16-byte boundary, one `image/jpeg` part per message with its length; a message that is not a whole JPEG (`FFD8…FFD9`) is dropped without ending the response, and the response carries `nosniff` and `Content-Security-Policy: sandbox; default-src 'none'`. `?as=` anything else, or over a file, is 422.

## Types

```ts
/** What a `getBlob` may ask for besides the bytes. */
interface GetBlobQuery {
  /** `"1"` from an agent relaying a stream: it says what the caller meant, never what the blob is. */
  live?: string;
  /** `"mjpeg"`: the stream as `multipart/x-mixed-replace`, one `image/jpeg` part per message. */
  as?: "mjpeg";
}
```
