{
  "openapi": "3.1.0",
  "info": {
    "title": "Throughline API — published /v1 surface",
    "version": "1.0.0",
    "summary": "Bundles, turnovers, media, purchase orders, storage ingest/filing, and Percep — the /api/v1 routes a key can call.",
    "description": "Throughline extends the experts who run a production — it never replaces them. This document covers the round-trip surface at https://docs.leeme.co/api.html#one-round-trip-clamped-to-the-caller (scene and character bundles, turnovers, media, purchase orders), plus the storage ingest/filing wire (`/v1/storage/events`, `/v1/storage/media-proxy`, `/v1/storage/tasks`, `/v1/storage/tasks/ack`), the Percep surface (`/v1/media-ticket`, `/v1/percep/session-token`, `/v1/percep/looks/{version_id}/brief`), and `POST /mcp` as an extension. The remaining resource-by-resource endpoints are documented in prose at https://docs.leeme.co/api.html.",
    "contact": {
      "name": "Throughline docs",
      "url": "https://docs.leeme.co"
    }
  },
  "externalDocs": {
    "description": "Full API reference (prose)",
    "url": "https://docs.leeme.co/api.html"
  },
  "servers": [
    {
      "url": "https://api.leeme.co",
      "description": "Pilot — provisional host. Will move to a leeme.co domain."
    }
  ],
  "tags": [
    { "name": "Bundles", "description": "One GET that assembles everything about one scene or character, server-side." },
    { "name": "Turnovers", "description": "A vendor engagement's own deliveries, and submitting work back on one." },
    { "name": "Media", "description": "A piece of media, read by id rather than by guessing a path." },
    { "name": "Purchase orders", "description": "A vendor's own purchase orders and its own cost-report line." },
    { "name": "Extension", "description": "Not part of the REST surface above — the same keys over a different transport." },
    { "name": "Storage", "description": "The mounted ingest agent's event wire and move feed. Write-scoped tl_ keys; no special door." },
    { "name": "Percep", "description": "Media tickets and on-set restyle helpers. Same tl_ keys and scopes as the rest of the plane." }
  ],
  "security": [ { "BearerAuth": [] } ],
  "paths": {
    "/api/v1/bundles/scene/{node_id}": {
      "get": {
        "operationId": "getSceneBundle",
        "tags": ["Bundles"],
        "summary": "Get a scene bundle",
        "description": "Everything about one scene in a single call: its script state, its linked assets and their released versions, its shoot days, its approvals, its department notes. Requires the `read` scope. A production-wide key gets the unclamped read GET /v1/assets/{id} already gives; an engagement-bound key gets exactly its own engagement's slice, and a scene it has no grant on 404s exactly like a wrong id would.",
        "parameters": [
          { "$ref": "#/components/parameters/NodeId" },
          { "$ref": "#/components/parameters/IfNoneMatch" }
        ],
        "responses": {
          "200": {
            "description": "Scene bundle.",
            "headers": { "ETag": { "$ref": "#/components/headers/ETag" } },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SceneBundle" } } }
          },
          "304": { "$ref": "#/components/responses/NotModified" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/v1/bundles/character/{node_id}": {
      "get": {
        "operationId": "getCharacterBundle",
        "tags": ["Bundles"],
        "summary": "Get a character bundle",
        "description": "A character's costumes plus the ingredient files that fed them — the relationship a pipeline can't reconstruct itself from a resource list. Requires `read`; reach-clamped the same way the scene bundle is.",
        "parameters": [
          { "$ref": "#/components/parameters/NodeId" },
          { "$ref": "#/components/parameters/IfNoneMatch" }
        ],
        "responses": {
          "200": {
            "description": "Character bundle.",
            "headers": { "ETag": { "$ref": "#/components/headers/ETag" } },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CharacterBundle" } } }
          },
          "304": { "$ref": "#/components/responses/NotModified" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/v1/turnovers": {
      "get": {
        "operationId": "listTurnovers",
        "tags": ["Turnovers"],
        "summary": "List this key's engagement turnovers",
        "description": "The deliveries this key's engagement holds a grant against — a `read` grant or a `submit` grant, either counts. Requires `read`. Empty (never an error) for a plain production-wide key, or for an engagement with no grant at all — this route has no meaning without an engagement to hold one.",
        "responses": {
          "200": {
            "description": "This engagement's turnovers. An empty array is a valid, non-error answer.",
            "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/TurnoverRow" } } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      }
    },
    "/api/v1/turnovers/{id}/submissions": {
      "post": {
        "operationId": "submitTurnoverWork",
        "tags": ["Turnovers"],
        "summary": "Submit work on a turnover",
        "description": "Work coming back on a delivery. Requires the `submit:turnover` scope on the key AND a live `submit` grant on THIS delivery for the key's engagement — either alone is refused. Every uploaded file becomes its own media object (see GET /v1/media/{id}) rather than a raw path handed back; nothing in the request names where a file lands on disk.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The delivery id.",
            "schema": { "type": "integer" }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": { "type": "string", "description": "Defaults to the delivery's own title if omitted." },
                  "note": { "type": "string", "description": "Free text attached to the submission." },
                  "files": {
                    "type": "array",
                    "items": { "type": "string", "format": "binary" },
                    "description": "One or more files. At least one is required."
                  }
                },
                "required": ["files"]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The submission that was recorded.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TurnoverSubmission" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": {
            "description": "Missing the `submit:turnover` scope, no engagement to submit as, or no live `submit` grant on this specific delivery.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "404": { "$ref": "#/components/responses/NotFound" },
          "422": {
            "description": "At least one file is required.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        }
      }
    },
    "/api/v1/media/{id}": {
      "get": {
        "operationId": "getMedia",
        "tags": ["Media"],
        "summary": "Get media metadata",
        "description": "A piece of media's metadata, read by id rather than by guessing a path. Requires `read`. Visible unconditionally to a production-wide key; for an engagement-bound key, work submitted on a turnover is visible only to the engagement that uploaded it — never through the delivery it was submitted against, even to another engagement holding a `read` grant on that same delivery.",
        "parameters": [ { "$ref": "#/components/parameters/MediaId" } ],
        "responses": {
          "200": {
            "description": "Media metadata.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MediaDetail" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/v1/media/{id}/content": {
      "get": {
        "operationId": "getMediaContent",
        "tags": ["Media"],
        "summary": "Stream media bytes",
        "description": "The bytes themselves, streamed. Same visibility rule and the same `read` scope as GET /v1/media/{id} — this is the one call that actually resolves the file.",
        "parameters": [ { "$ref": "#/components/parameters/MediaId" } ],
        "responses": {
          "200": {
            "description": "The file's raw bytes. Content-Type depends on the underlying file.",
            "content": { "application/octet-stream": { "schema": { "type": "string", "format": "binary" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/v1/purchase-orders": {
      "get": {
        "operationId": "getPurchaseOrders",
        "tags": ["Purchase orders"],
        "summary": "Get this vendor's purchase orders and cost-report line",
        "description": "A vendor's own purchase orders and its own cost-report line — nothing else money-shaped, never a production's whole topsheet and never another vendor's rows. Requires the `read:money` scope AND a live `read:own_po` grant on the engagement. A key without the scope is refused with a 403 like any other missing scope; a key that holds the scope but has no engagement, or an engagement with no money grant, answers with an empty result rather than an error.",
        "responses": {
          "200": {
            "description": "This vendor's purchase orders and cost-report line. Empty arrays and a null week_of are valid, non-error answers for an engagement with no money grant.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PurchaseOrdersResponse" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      }
    },

    "/api/v1/media-ticket": {
      "get": {
        "operationId": "getMediaTicket",
        "tags": ["Percep"],
        "summary": "Mint a short-lived media ticket",
        "description": "A ticket for THIS key's production, used to fetch bytes from the off-plane file route (`GET /media/{path}?t=\u2026`), which trusts the ticket and never a bearer header. Requires `read`.",
        "responses": {
          "200": {
            "description": "Ticket, expiry, and the production it is bound to.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MediaTicket" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      }
    },
    "/api/v1/percep/session-token": {
      "post": {
        "operationId": "mintPercepSessionToken",
        "tags": ["Percep"],
        "summary": "Mint a short-lived Decart client token",
        "description": "Exchanges the server-side Decart key for a client token so the permanent credential never leaves this backend. Requires `read`. A deploy with no Decart key answers 503; a key Decart itself refuses is 502.",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "model": { "type": "string", "enum": ["lucy-2.5", "lucy-restyle-2"], "description": "Defaults to lucy-2.5." }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Client token.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PercepSessionToken" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "422": { "$ref": "#/components/responses/Unprocessable" },
          "502": { "description": "Decart refused the platform key.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "503": { "description": "This deploy has no Decart key.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/v1/percep/looks/{version_id}/brief": {
      "post": {
        "operationId": "distillPercepLookBrief",
        "tags": ["Percep"],
        "summary": "Distill a look brief from a Released still",
        "description": "A structured look brief (`environment`, `palette`, `lighting`, `era`, `materials`, `texture`) from a Released still. An unknown, foreign, or unreleased version is the same 404. Requires `read`. Cached per version after the first call.",
        "parameters": [
          { "name": "version_id", "in": "path", "required": true, "description": "The AssetVersion id.", "schema": { "type": "integer" } }
        ],
        "responses": {
          "200": {
            "description": "Look brief.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LookBrief" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "422": { "$ref": "#/components/responses/Unprocessable" },
          "502": { "description": "Provider-side failure that is not about this image.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "503": { "description": "Look brief unavailable.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/v1/storage/events": {
      "post": {
        "operationId": "recordStorageEvents",
        "tags": ["Storage"],
        "summary": "Land a batch of filespace events",
        "description": "The storage-plane ingest wire. The mounted agent POSTs `{filespace, events}` batches. Each event carries the agent's own `dedupe_key`; a replay counts as a duplicate, never a second row. Per-event problems land in `refused`. Requires `write`. The batch's `filespace` must match this key's show's active install.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/StorageEventBatch" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Per-batch counts plus any per-event refusals.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/StorageEventAck" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "409": { "description": "This show has no active storage.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "413": { "description": "Batch too large.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "422": { "$ref": "#/components/responses/Unprocessable" }
        }
      }
    },
    "/api/v1/storage/media-proxy": {
      "post": {
        "operationId": "putStorageMediaProxy",
        "tags": ["Storage"],
        "summary": "Overwrite a flip stand-in with a bounded JPEG",
        "description": "The mounted agent POSTs a downsized JPEG for a filespace create. Body is JSON `{filespace, path, bytes}` (`bytes` is base64) or multipart with the same fields. The `AssetFile` row does not change. Requires `write`. The `filespace` must match this key's show's active install. A body over ~8 MB is refused.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/StorageMediaProxy" }
            },
            "multipart/form-data": {
              "schema": { "$ref": "#/components/schemas/StorageMediaProxy" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Bytes written at the ticket path.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/StorageMediaProxyAck" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "409": { "description": "This show has no active storage.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "413": { "description": "Body over the proxy ceiling.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "422": { "$ref": "#/components/responses/Unprocessable" }
        }
      }
    },
    "/api/v1/storage/tasks": {
      "get": {
        "operationId": "listStorageTasks",
        "tags": ["Storage"],
        "summary": "The agent's approved move feed",
        "description": "Admin-approved file moves, oldest first. Requires `write` — the same agent credential that ships events.",
        "responses": {
          "200": {
            "description": "Pending move tasks.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/StorageTaskFeed" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      }
    },
    "/api/v1/storage/tasks/ack": {
      "post": {
        "operationId": "ackStorageTask",
        "tags": ["Storage"],
        "summary": "Acknowledge one move task",
        "description": "One task's outcome: `{task_id, ok, note?}`. Re-acking a settled task answers ok. A pre-move fingerprint is `{task_id, src_sha256}` (64 hex chars) without `ok`. Requires `write`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/StorageTaskAck" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Ack accepted.",
            "content": { "application/json": { "schema": { "type": "object", "properties": { "ok": { "type": "boolean" }, "fingerprinted": { "type": "boolean" } }, "required": ["ok"] } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "422": { "$ref": "#/components/responses/Unprocessable" }
        }
      }
    },
    "/mcp": {
      "post": {
        "operationId": "mcpCall",
        "tags": ["Extension"],
        "x-throughline-extension": true,
        "summary": "MCP (Model Context Protocol) endpoint",
        "description": "An MCP server onto this same v1 surface, over Streamable HTTP: one JSON-RPC 2.0 message per request body, no batching, no SSE fallback. The MCP layer resolves the same bearer key and calls the identical function the matching REST endpoint calls, so a key gets the same clamped answer and the same refusal on either door. This entry is a pointer, not a full spec of the fifteen tools, six resources, and four prompts behind it — see https://docs.leeme.co/mcp.html for the tool catalogue and https://docs.leeme.co/api.html for what each tool's REST equivalent returns.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/JsonRpcRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A JSON-RPC result or error object. Auth, scope, engagement-door, and tenancy refusals arrive as REST's own bytes unwrapped (see the 401/403 responses below) rather than a JSON-RPC error envelope; a bad method or tool/resource/prompt name gets a JSON-RPC error object instead, because there's no REST call for those to match.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JsonRpcResponse" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "tl_...",
        "description": "A Throughline access key: `Authorization: Bearer tl_...`. Minted by a production's admin from Settings — production-wide (reads/writes the whole production) or engagement-bound (clamped to one vendor organization's own engagement). One key, one production; there is no cross-production key. See https://docs.leeme.co/keys.html."
      }
    },
    "parameters": {
      "NodeId": {
        "name": "node_id",
        "in": "path",
        "required": true,
        "description": "The graph node id of the scene or character.",
        "schema": { "type": "integer" }
      },
      "MediaId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "The media object's id.",
        "schema": { "type": "integer" }
      },
      "IfNoneMatch": {
        "name": "If-None-Match",
        "in": "header",
        "required": false,
        "description": "An ETag from a previous response to this same bundle. A match returns a bare 304 instead of re-sending the whole document.",
        "schema": { "type": "string" }
      }
    },
    "headers": {
      "ETag": {
        "description": "A stable hash of the assembled document. Send it back as If-None-Match on the next request.",
        "schema": { "type": "string" }
      }
    },
    "responses": {
      "NotModified": {
        "description": "Not modified — the caller's If-None-Match matched the current ETag. No body.",
        "headers": { "ETag": { "$ref": "#/components/headers/ETag" } }
      },
      "Unauthorized": {
        "description": "Missing or malformed Authorization header, or an unknown/revoked key.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Forbidden": {
        "description": "The key lacks the scope this endpoint needs, or an engagement-bound key called an endpoint outside the bundle/turnover/media/purchase-order surface.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "NotFound": {
        "description": "No such object, OR the object exists but this key's engagement has no grant reaching it — the two cases are deliberately indistinguishable to the caller.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Unprocessable": {
        "description": "The request body was missing a required field or failed a named check.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "description": "The standard error body every refusal on this surface returns.",
        "properties": {
          "detail": { "type": "string" }
        },
        "required": ["detail"]
      },
      "Scene": {
        "type": "object",
        "properties": {
          "node_id": { "type": "integer" },
          "number": { "type": "string" },
          "slugline": { "type": "string" },
          "synopsis": { "type": "string" },
          "status": { "type": "string", "description": "Not a closed set on this object: besides the planned/active/blocked/done life cycle a script revision can also mark a scene omitted, so no enum is declared here (unlike the write-side status accepted by POST /assets, which never targets scenes)." },
          "revision_color": { "type": "string" },
          "elements": { "type": "object", "additionalProperties": true },
          "episode_id": { "type": "integer", "description": "Present only on an episodic production." },
          "episode_code": { "type": "string", "description": "Present only on an episodic production, e.g. \"S2E7\"." }
        },
        "required": ["node_id", "number", "slugline", "status", "revision_color", "elements"],
        "additionalProperties": true
      },
      "AssetVersionSummary": {
        "type": "object",
        "description": "One released version of an asset, as it rides inside a bundle's released_versions list.",
        "properties": {
          "id": { "type": "integer" },
          "version": { "type": "integer" },
          "variant": { "type": "string" },
          "status": { "type": "string" },
          "note": { "type": ["string", "null"] },
          "media_path": { "type": ["string", "null"] },
          "created_by": { "type": ["string", "null"] },
          "created_by_person_id": { "type": ["integer", "null"] },
          "archived": { "type": "boolean" },
          "created_at": { "type": ["string", "null"], "format": "date-time" },
          "released": { "type": "boolean", "description": "Always true inside a bundle's released_versions — unreleased versions never appear here." }
        },
        "required": ["id", "version", "variant", "status", "released"],
        "additionalProperties": true
      },
      "SceneAsset": {
        "type": "object",
        "properties": {
          "node_id": { "type": "integer" },
          "label": { "type": "string" },
          "kind": { "type": "string" },
          "status": { "type": "string" },
          "released_versions": { "type": "array", "items": { "$ref": "#/components/schemas/AssetVersionSummary" } }
        },
        "required": ["node_id", "label", "kind", "status", "released_versions"]
      },
      "ShootDayRow": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "day_number": { "type": "integer" },
          "date": { "type": "string" },
          "status": { "type": "string" },
          "general_call": { "type": ["string", "null"] }
        },
        "required": ["id", "day_number", "date", "status"]
      },
      "ApprovalRow": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "title": { "type": "string" },
          "status": { "type": "string" },
          "mode": { "type": "string" },
          "due": { "type": ["string", "null"] }
        },
        "required": ["id", "title", "status", "mode"]
      },
      "DepartmentNoteRow": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "summary": { "type": "string" },
          "created_at": { "type": ["string", "null"], "format": "date-time" }
        },
        "required": ["id", "summary"]
      },
      "SceneBundle": {
        "type": "object",
        "properties": {
          "version": { "type": "integer", "description": "The bundle document's own shape version." },
          "scene": { "$ref": "#/components/schemas/Scene" },
          "assets": { "type": "array", "items": { "$ref": "#/components/schemas/SceneAsset" } },
          "shoot_days": { "type": "array", "items": { "$ref": "#/components/schemas/ShootDayRow" } },
          "approvals": { "type": "array", "items": { "$ref": "#/components/schemas/ApprovalRow" } },
          "department_notes": { "type": "array", "items": { "$ref": "#/components/schemas/DepartmentNoteRow" } },
          "etag": { "type": "string" }
        },
        "required": ["version", "scene", "assets", "shoot_days", "approvals", "department_notes", "etag"]
      },
      "Ingredient": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "kind": { "type": "string" },
          "label": { "type": "string" },
          "relation": { "type": ["string", "null"] },
          "media_path": { "type": ["string", "null"] }
        },
        "required": ["id", "kind", "label"]
      },
      "Costume": {
        "type": "object",
        "properties": {
          "node_id": { "type": "integer" },
          "label": { "type": "string" },
          "status": { "type": "string" },
          "released_versions": { "type": "array", "items": { "$ref": "#/components/schemas/AssetVersionSummary" } },
          "ingredients": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/Ingredient" },
            "description": "De-duped union of every released version's own ingredients."
          }
        },
        "required": ["node_id", "label", "status", "released_versions", "ingredients"]
      },
      "CharacterBundle": {
        "type": "object",
        "properties": {
          "version": { "type": "integer" },
          "character": {
            "type": "object",
            "properties": {
              "node_id": { "type": "integer" },
              "label": { "type": "string" },
              "status": { "type": "string" }
            },
            "required": ["node_id", "label", "status"]
          },
          "costumes": { "type": "array", "items": { "$ref": "#/components/schemas/Costume" } },
          "etag": { "type": "string" }
        },
        "required": ["version", "character", "costumes", "etag"]
      },
      "TurnoverRow": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "title": { "type": "string" },
          "status": { "type": "string", "enum": ["draft", "blocked", "ready", "sent", "accepted"] },
          "due": { "type": "string" },
          "external_to": { "type": "string" },
          "spec": { "type": "array", "items": { "type": "object", "additionalProperties": true } },
          "sent_at": { "type": ["string", "null"], "format": "date-time" },
          "can_submit": {
            "type": "boolean",
            "description": "Whether THIS engagement specifically holds a submit grant on this delivery. A read-only grant lists the delivery here but answers false."
          }
        },
        "required": ["id", "title", "status", "spec", "can_submit"]
      },
      "SubmissionMediaRow": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "label": { "type": "string" },
          "kind": { "type": "string" }
        },
        "required": ["id", "label", "kind"]
      },
      "TurnoverSubmission": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "delivery_id": { "type": "integer" },
          "status": { "type": "string" },
          "title": { "type": "string" },
          "note": { "type": "string" },
          "media": { "type": "array", "items": { "$ref": "#/components/schemas/SubmissionMediaRow" } },
          "created_at": { "type": ["string", "null"], "format": "date-time" }
        },
        "required": ["id", "delivery_id", "status", "title", "media"]
      },
      "MediaDetail": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "kind": { "type": "string" },
          "label": { "type": "string" },
          "node_id": { "type": ["integer", "null"] },
          "delivery_id": { "type": ["integer", "null"] },
          "work_item_id": { "type": ["integer", "null"], "description": "Set only for media submitted on a turnover." },
          "created_by": { "type": ["string", "null"] },
          "created_at": { "type": ["string", "null"], "format": "date-time" }
        },
        "required": ["id", "kind", "label"]
      },
      "PurchaseOrderRow": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "department_id": { "type": ["integer", "null"], "description": "Null for a production-wide commitment (an insurance rider, a completion-bond fee) that belongs to nobody's envelope — and so is never returned here." },
          "account": { "type": "string" },
          "vendor": { "type": "string" },
          "description": { "type": "string" },
          "amount": { "type": "number" },
          "status": { "type": "string", "enum": ["open", "partial", "closed"] },
          "date": { "type": "string" }
        },
        "required": ["id", "account", "vendor", "description", "amount", "status", "date"]
      },
      "CostReportLine": {
        "type": "object",
        "description": "One account line off the production's active weekly cost report, restricted to the accounts this engagement's granted department(s) cover.",
        "properties": {
          "account": { "type": "string" },
          "budget": { "type": "number" }
        },
        "required": ["account"],
        "additionalProperties": true
      },
      "PurchaseOrdersResponse": {
        "type": "object",
        "properties": {
          "purchase_orders": { "type": "array", "items": { "$ref": "#/components/schemas/PurchaseOrderRow" } },
          "line": { "type": "array", "items": { "$ref": "#/components/schemas/CostReportLine" } },
          "week_of": { "type": ["string", "null"] }
        },
        "required": ["purchase_orders", "line", "week_of"]
      },
      "JsonRpcRequest": {
        "type": "object",
        "description": "A single JSON-RPC 2.0 request object. Batched arrays are not supported (-32600).",
        "properties": {
          "jsonrpc": { "const": "2.0" },
          "id": { "anyOf": [{ "type": "string" }, { "type": "integer" }] },
          "method": {
            "type": "string",
            "description": "initialize | tools/list | resources/list | prompts/list | prompts/get | tools/call | resources/read"
          },
          "params": { "type": "object", "additionalProperties": true }
        },
        "required": ["jsonrpc", "method"]
      },
      "JsonRpcResponse": {
        "type": "object",
        "properties": {
          "jsonrpc": { "const": "2.0" },
          "id": { "anyOf": [{ "type": "string" }, { "type": "integer" }, { "type": "null" }] },
          "result": { "type": "object", "additionalProperties": true },
          "error": {
            "type": "object",
            "properties": {
              "code": { "type": "integer", "enum": [-32700, -32600, -32601, -32602] },
              "message": { "type": "string" }
            },
            "required": ["code", "message"]
          }
        },
        "required": ["jsonrpc", "id"]
      },
      "MediaTicket": {
        "type": "object",
        "properties": {
          "ticket": { "type": "string" },
          "expires_at": { "type": "string" },
          "production_id": { "type": "integer" }
        },
        "required": ["ticket", "expires_at", "production_id"]
      },
      "PercepSessionToken": {
        "type": "object",
        "properties": {
          "token": { "type": "string" },
          "expires_at": { "type": "string" },
          "model": { "type": "string" }
        },
        "required": ["token", "expires_at", "model"]
      },
      "LookBrief": {
        "type": "object",
        "properties": {
          "environment": { "type": "string" },
          "palette": { "type": "string" },
          "lighting": { "type": "string" },
          "era": { "type": "string" },
          "materials": { "type": "string" },
          "texture": { "type": "string" }
        },
        "additionalProperties": true
      },
      "StorageEventBatch": {
        "type": "object",
        "properties": {
          "filespace": { "type": "string" },
          "events": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "event_type": { "type": "string", "enum": ["create", "modify", "delete"] },
                "path": { "type": "string" },
                "source": { "type": "string", "enum": ["audit_tail", "reconciler"] },
                "dedupe_key": { "type": "string" },
                "detected_at": { "type": "string" },
                "size": { "type": ["integer", "null"] },
                "mtime_ns": { "type": ["integer", "null"] }
              },
              "required": ["event_type", "path", "source", "dedupe_key", "detected_at"]
            }
          }
        },
        "required": ["filespace", "events"]
      },
      "StorageMediaProxy": {
        "type": "object",
        "properties": {
          "filespace": { "type": "string" },
          "path": { "type": "string" },
          "bytes": { "type": "string", "description": "Base64 JPEG (JSON) or the file itself (multipart)." }
        },
        "required": ["filespace", "path", "bytes"]
      },
      "StorageMediaProxyAck": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean" },
          "media_path": { "type": "string" },
          "bytes": { "type": "integer" }
        },
        "required": ["ok", "media_path", "bytes"]
      },
      "StorageEventAck": {
        "type": "object",
        "properties": {
          "accepted": { "type": "integer" },
          "duplicates": { "type": "integer" },
          "refused": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "index": { "type": "integer" },
                "reason": { "type": "string" }
              }
            }
          }
        },
        "required": ["accepted", "duplicates"]
      },
      "StorageTaskFeed": {
        "type": "object",
        "properties": {
          "tasks": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "task_id": { "type": "integer" },
                "src": { "type": "string" },
                "dst": { "type": "string" }
              },
              "required": ["task_id", "src", "dst"]
            }
          }
        },
        "required": ["tasks"]
      },
      "StorageTaskAck": {
        "type": "object",
        "properties": {
          "task_id": { "type": "integer" },
          "ok": { "type": "boolean" },
          "note": { "type": "string" },
          "src_sha256": { "type": "string", "description": "64 hex chars. Required to fingerprint when `ok` is omitted." }
        },
        "required": ["task_id"]
      }
    }
  }
}
