# Sidecars

JSON metadata on every version, by namespace. The built-in schemas, writing your own, and keeping a namespace to one tool.

A sidecar is one JSON document attached to one **version** of a file, under a **namespace**. A video's version can carry its media facts, a transcript, AI tags, the prompt that generated it, people's ratings and your QC results, each in its own namespace and each written by whoever owns that job.

Sidecars are what search reads, what the apps show under **Info**, what [compare](/docs/files#versions) diffs, and what AI agents read through MCP instead of downloading the file.

## Read and write

| To | Call |
|---|---|
| Every sidecar of a version | `GET /v1/spaces/{sid}/versions/{vid}/sidecars` |
| One namespace | `GET /v1/spaces/{sid}/versions/{vid}/sidecars/{ns}` |
| Write (replaces that namespace's document) | `PUT /v1/spaces/{sid}/versions/{vid}/sidecars/{ns}` with `{"doc": {…}}` |
| Remove | `DELETE /v1/spaces/{sid}/versions/{vid}/sidecars/{ns}` |

```sh tab=curl
curl -X PUT "https://side.example.com/v1/spaces/$SPACE/versions/$VERSION/sidecars/x-acme.qc" \
  -H "Authorization: Bearer $SIDESPACE_TOKEN" -H "Content-Type: application/json" \
  -d '{"doc": {"passed": false, "issues": [{"at_s": 12.4, "kind": "dropped_frame"}]}, "producer": {"name": "acme-qc", "version": "1.4"}}'
```

```ts tab=TypeScript
await side.putSidecar("Film A", "/Ep01/cut.mp4", "x-acme.qc", { passed: false, issues: [{ at_s: 12.4, kind: "dropped_frame" }] });
const tags = await side.getSidecar("Film A", "/Ep01/cut.mp4", "ai.tags");
```

```python tab=Python
side.put_sidecar("Film A", "/Ep01/cut.mp4", "x-acme.qc", {"passed": False, "issues": [{"at_s": 12.4, "kind": "dropped_frame"}]})
tags = side.get_sidecar("Film A", "/Ep01/cut.mp4", "ai.tags")
```

```sh tab=CLI
sidespace sidecar put "Film A" Ep01/cut.mp4 x-acme.qc '{"passed": false}'
sidespace sidecar get "Film A" Ep01/cut.mp4 ai.tags
```

Writing needs editor rights and the `sidecars:write` scope, or `sidecars:write:<ns>` for one namespace (`sidecars:write:x-acme.*` for a family). `producer` records which tool wrote it; the apps show it.

## Built-in namespaces

Built-in namespaces are checked against a JSON Schema on write (in `proto/sidecar/` in the repository), so every tool can rely on their shape.

| Namespace | Written by | Holds |
|---|---|---|
| `core` | The server | `content_hash`, `size`, `mime`, `author_id`, `device_id`, `client_mtime`, `created_at` |
| `media` | The server (ffprobe) | `duration_s`, `container`, `bitrate`, `width`, `height` (as displayed), `fps`, `video_codec`, `audio_codec`, `audio_channels`, `creation_time` |
| `transcript` | `side.transcribe`, or your tool | `text`, `language`, `duration`, `segments` (`start`, `end`, `text`, `speaker`), `model`, `provider` |
| `ai.tags` | `side.tags`, or your tool | `description`, `tags`, `shot_type`, `setting`, `mood`, `people`, `text_on_screen`, `frames`, `model` |
| `ai.generation` | The tool that generated the asset | `tool` (required), `model`, `provider`, `prompt`, `negative_prompt`, `seed`, `parameters`, `inputs`, `job_id`, `cost`, `regenerate_url` |
| `user` | People in the apps | `tags`, `rating`, `label`, `note`, `status` (draft, review, approved…) |
| `derived` | Actions that make files | `from` (`node_id`, `version_id`, `path`), `action`, `params`, `run_id` |

`core` and `media` are written only by the server. When a new version is saved, its `user` and `ai.generation` sidecars are carried over from the previous version (with `producer.inherited_from`), because a re-export keeps the same prompt and the same rating; the others describe the bytes and are made again.

### Recording how an AI asset was made

If your tool generates images or video, write `ai.generation` when you upload. Search finds files by their prompt, and the apps show the model and a link back to regenerate.

```json
{
  "tool": "cliptap",
  "model": "video-model-x",
  "prompt": "A fishing harbor at night, slow dolly in, rain",
  "seed": 1234,
  "inputs": [{ "role": "reference", "node_id": "nod_…", "version_id": "ver_…" }],
  "regenerate_url": "https://cliptap.example/jobs/9f2c"
}
```

## Your own namespaces

Custom namespaces start with `x-`, usually `x-<company>.<tool>` (`x-acme.qc`). Any JSON object up to the server's size limit is accepted; nothing validates it but you.

> [!TIP]
> Prefer a sidecar to a new file next to the original. It stays attached to the right version, it is searchable, and moving or renaming the file doesn't break it.

## One writer per namespace

A space manager can hand a namespace to one member, usually an [agent](/docs/authentication#agents). From then on only that member can write it in that space and everyone else can only read it, so two AI tools can't overwrite each other's results.

```sh
sidespace namespace claim "Film A" x-qc "Clip Tagger"
sidespace namespace list "Film A"
sidespace namespace release "Film A" x-qc
```

`ai.generation` and `x-…` namespaces can be claimed. `core` and `media` belong to the server, and `user` is everyone's.
