{"openapi":"3.1.0","jsonSchemaDialect":"https://json-schema.org/draft/2020-12/schema","info":{"title":"Space Centre API","version":"1.0.0","summary":"Public catalog and internal writer contract for the launch window.","description":"Canonical HTTP contract. This file is the only OpenAPI source of truth.\nThe API serves it at `GET /openapi.json`. Hono does not emit a spec.\n\n**Listeners.** Local `just dev` binds `127.0.0.1:8080` with every path.\nProd public `:8080` (Anycast and Flycast `:80` — same Fly Proxy map) serves\nonly public paths (`/`, `/about`, `/v1/timeline*`, `/v1/launches`, `/v1/vehicles`,\n`/v1/window`, `/v1/health`, `/v1/ready`, `/docs`, `/assets/*`, `/openapi.json`);\n`/v1/internal/*` and `/v1/admin/*` on that listener return **404** (not 401).\nThe full app is unpublished Machine `:8081`:\n`http://space-centre-api.internal:8081` (6PN) or `fly proxy 8081:8081`.\nDo **not** send writes to `http://space-centre-api.flycast`. Writes still\nneed a hashed key and the per-route scope.\n\n**Auth.** `Authorization: Bearer sc_live_<22>` or `sc_test_<22>` (url-safe,\n≥128 bits). Example live key: `sc_live_ab12cd34ef56gh78ij90kl`. Public GETs\nare unauthenticated. Write routes always require a key.\n\n**Per-route scopes.** The tag or section header is not an \"or\". A\n`launches:write` key that hits `/v1/admin/*`, merge, approve, or `:batch`\ngets **403**.\n\n| Scope | Who | Capability |\n| --- | --- | --- |\n| _(none)_ | website / anonymous | public GETs; 60 req/min / IP |\n| `launches:read` | external clients | same GETs; 600 req/min |\n| `launches:write` | Agent; Researcher incremental upserts | `/v1/internal/launches*` except merge, `/v1/internal/agent/*`, prune |\n| `admin` | operators | `/v1/admin/keys*`, merge, summary approve, `:batch`, admin run reads |\n\n**Rate-limit headers** on `/v1/*` and `/openapi.json`, including 401/429:\n`X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` (unix seconds).\n`GET /`, `GET /about`, and `GET /assets/*` are unmetered and omit those\nheaders (a rail load of visual assets would blow the anonymous 60/min bucket).\nAdmin is 600 req/min so promote is not stuck behind the write anti-loop (120).\n\n**Index class predicate.** `class` omitted is `orbital`. `includeFeatured`\ndefaults **false**. Explicit `class` + `includeFeatured=false` is that class\nonly. `class` + `includeFeatured=true` is that class **OR** `featured` rows\nof other classes (reserved; v1 does not send this). Site default:\n`class=orbital&includeFeatured=false`. Catalog `GET /v1/launches?featured=`\nis independent of the rail.\n\n**Cursors are opaque.** `nextCursor` is URL-safe base64 of a versioned\n`{ v, net, publicId }` payload (`net` is the bucket-start instant). Clients\nmust not parse it. The API never accepts `offset`. Follow `nextCursor` on\n`GET /v1/timeline` until `to` is covered or the cursor is absent.\n\n**Idempotency.** Writers send `Idempotency-Key`. Replay returns the stored\nstatus and body for 24h.\n\n**Optimistic concurrency.** `PATCH /v1/internal/launches/{id}` and\n`PATCH .../visual` require `If-Match: <updated_at>` or `*` on first write.\n409 on mismatch. `:batch` does not use `If-Match`; it is cutover-only\noverwrite and returns 409 `{ \"error\": \"agent_live\" }` when\n`AGENT_WRITES_ENABLED=true`.\n\nEntity bodies `$ref` `packages/contracts/schema`. Compact timeline/index\ncards use the camelCase layout fields shown here.\n","license":{"name":"UNLICENSED"}},"servers":[{"url":"/","description":"Same-origin public tree. Local all-routes process is http://127.0.0.1:8080. Prod Flycast :80 is this public tree ([http_service] → :8080); /v1/internal and /v1/admin return 404. Do not send writes here.\n"},{"url":"http://space-centre-api.internal:8081","description":"Prod unpublished full app (6PN, no TLS). Agent SC_API_BASE. Laptop: fly proxy 8081:8081 then http://127.0.0.1:8081.\n"}],"tags":[{"name":"Timeline","description":"Layout index and windowed cards. Public, unauthenticated."},{"name":"Launches","description":"Catalog search, card, sources, summary, visual. Public GETs."},{"name":"Vehicles","description":"Families, configs, stage templates. Public."},{"name":"Window","description":"Anonymous Watch and takeoff tallies for Cadence. Plays, not unique people. Not catalog stats (`/v1/stats` stays out).\n"},{"name":"Health","description":"Liveness and readiness."},{"name":"Spec","description":"Committed OpenAPI document."},{"name":"Internal launches","description":"Writer upserts. Scope is per operation (`launches:write` or `admin`). Not bound on the prod public listener.\n"},{"name":"Internal agent","description":"Agent run and invocation writes. `launches:write` only. No `/v1/admin`."},{"name":"Internal maintenance","description":"Prune usage events and expired idempotency keys. `launches:write`."},{"name":"Admin keys","description":"Mint, list, revoke API keys. `admin` only."},{"name":"Admin agent","description":"Operator debug mirror of agent runs. The Agent does not call this."}],"security":[],"paths":{"/v1/timeline/index":{"get":{"tags":["Timeline"],"operationId":"getTimelineIndex","summary":"Compact layout index","description":"Full compact set for x-position, content width, year ticks, and clusters.\nNo `limit`. Client keeps this in memory and hydrates cards via\n`GET /v1/timeline`. Cache `Cache-Control: public, max-age=60` plus\n`ETag` on `max(updated_at)`.\n","parameters":[{"$ref":"#/components/parameters/Class"},{"$ref":"#/components/parameters/IncludeFeatured"}],"responses":{"200":{"description":"Compact index for the resolved filter.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControl"},"ETag":{"$ref":"#/components/headers/ETag"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TimelineIndex"},"example":{"generatedAt":"2026-08-18T06:12:00Z","filter":{"class":"orbital","includeFeatured":false},"count":10042,"items":[{"net":"1957-10-04T19:28:34Z","publicId":"1957-001","familyId":"r-7","status":"success","class":"orbital","greyed":false,"featured":false}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/timeline":{"get":{"tags":["Timeline"],"operationId":"getTimeline","summary":"Windowed timeline cards","description":"On-screen cards only. Same class / includeFeatured predicate as the index.\n`limit` default 100, max 200. Follow `nextCursor` until `to` is covered\nor `nextCursor` is absent. Never send `offset`.\n","parameters":[{"$ref":"#/components/parameters/FromDate"},{"$ref":"#/components/parameters/ToDate"},{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit200"},{"$ref":"#/components/parameters/Class"},{"$ref":"#/components/parameters/IncludeFeatured"}],"responses":{"200":{"description":"Card page for the requested window.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TimelinePage"},"example":{"items":[{"publicId":"1957-001","net":"1957-10-04T19:28:34Z","netPrecision":"second","name":"Sputnik 1","vehicleFamily":"r-7","lodAssetId":"r-7/sputnik","status":"success","greyed":false,"featured":false,"hasAnimation":true,"class":"orbital"}],"nextCursor":"aXY0cXN3eY2lk","from":"1957-10-01","to":"1958-01-01"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/launches":{"get":{"tags":["Launches"],"operationId":"listLaunches","summary":"Catalog search or external-id lookup","description":"`from` / `to` filter **`net`** only. `updatedSince` filters\n**`updated_at`** (Agent \"our rows that changed\"). `externalSystem` and\n`externalValue` are required together and look up `external_ids`: **200**\nwith the matching card plus `id`, `publicId`, `externalIds`, or **404**.\nOther list filters are ignored on that point lookup.\n","parameters":[{"$ref":"#/components/parameters/FromDate"},{"$ref":"#/components/parameters/ToDate"},{"$ref":"#/components/parameters/UpdatedSince"},{"$ref":"#/components/parameters/LaunchStatus"},{"$ref":"#/components/parameters/Q"},{"$ref":"#/components/parameters/VehicleFamily"},{"$ref":"#/components/parameters/Featured"},{"$ref":"#/components/parameters/ExternalSystem"},{"$ref":"#/components/parameters/ExternalValue"},{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit200"}],"responses":{"200":{"description":"Catalog page, or a single lookup card when externalSystem+externalValue are set.\n","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/LaunchListResponse"},{"$ref":"#/components/schemas/LaunchLookup"}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/launches/{id}":{"get":{"tags":["Launches"],"operationId":"getLaunch","summary":"Full launch by public_id or UUID","description":"`{id}` is `public_id` or internal UUID. A merge loser returns **200**\nwith `redirectTo` (survivor `public_id`) and the survivor payload.\nNo 301 — `fetch` default `redirect: follow` would drop a 301 body.\n","parameters":[{"$ref":"#/components/parameters/LaunchId"}],"responses":{"200":{"description":"Launch detail. `redirectTo` is set when `{id}` was a loser.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LaunchDetail"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/launches/{id}/sources":{"get":{"tags":["Launches"],"operationId":"getLaunchSources","summary":"Primary-source index for a launch","parameters":[{"$ref":"#/components/parameters/LaunchId"}],"responses":{"200":{"description":"Sources attached to this launch (or its survivor).","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LaunchSourcesResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/launches/{id}/summary":{"get":{"tags":["Launches"],"operationId":"getLaunchSummary","summary":"Current summary plus pendingReview","description":"Returns the `is_current` row (or null — card then uses a factual status\nline). `pendingReview` is true when a newer `needs_review` row exists.\n","parameters":[{"$ref":"#/components/parameters/LaunchId"}],"responses":{"200":{"description":"Current summary envelope.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LaunchSummaryResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/launches/{id}/visual":{"get":{"tags":["Launches"],"operationId":"getLaunchVisual","summary":"Family spec join plus binding and recoveries","description":"Generated family JSON from `packages/visual-spec/gen` joined with\n`launch_visual_bindings` and `booster_recoveries`. No SVG body. Missing\nbinding ⇒ family `_generic`.\n","parameters":[{"$ref":"#/components/parameters/LaunchId"}],"responses":{"200":{"description":"Visual join payload.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LaunchVisualResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/vehicles":{"get":{"tags":["Vehicles"],"operationId":"listVehicles","summary":"Vehicle families and configs","responses":{"200":{"description":"Families with their configs.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VehicleListResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/vehicles/{familyId}":{"get":{"tags":["Vehicles"],"operationId":"getVehicleFamily","summary":"Family, configs, and stage templates","parameters":[{"$ref":"#/components/parameters/FamilyId"}],"responses":{"200":{"description":"Family detail.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VehicleFamilyDetail"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/window":{"get":{"tags":["Window"],"operationId":"getWindow","summary":"Watch and takeoff tallies","description":"Production tallies: Watch openings, Watch completions (last beat reached), takeoff plays, and the busiest flights by `publicId`. Cadence always reads this from space-centre.org. GET is CORS `*` so a local Cadence still reports production. Not unique visitors.\n","responses":{"200":{"description":"Current tallies.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"Cache-Control":{"$ref":"#/components/headers/CacheControl"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WindowSnapshot"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"post":{"tags":["Window"],"operationId":"postWindow","summary":"Record a Watch or takeoff","description":"Anonymous increment on the live site. `play` with `publicId` also bumps that flight. Rate-limited with other public `/v1` calls. Localhost, Tailscale, Look, and Console Open must not send this.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WindowEvent"}}}},"responses":{"204":{"description":"Counted.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/health":{"get":{"tags":["Health"],"operationId":"getHealth","summary":"Liveness","description":"Process is up. Does not touch Postgres.","responses":{"200":{"description":"Process is serving.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}}}}}},"/v1/ready":{"get":{"tags":["Health"],"operationId":"getReady","summary":"Readiness","description":"SELECT 1 against Postgres. 503 if Neon / local Postgres is down.","responses":{"200":{"description":"Database reachable.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}}},"503":{"$ref":"#/components/responses/NotReady"}}}},"/openapi.json":{"get":{"tags":["Spec"],"operationId":"getOpenApi","summary":"Committed OpenAPI document","description":"This file, served as JSON (YAML converted or a CI-generated sibling).\nNot emitted from routes.\n","responses":{"200":{"description":"OpenAPI 3 document.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}}},"/v1/internal/launches":{"post":{"tags":["Internal launches"],"operationId":"upsertLaunch","summary":"Upsert one launch","description":"Create or update by `public_id` / external id. Idempotent.\nScope `launches:write`.\n","x-scope":"launches:write","security":[{"BearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LaunchDraft"}}}},"responses":{"200":{"description":"Upserted launch row.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Launch"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/internal/launches:batch":{"post":{"tags":["Internal launches"],"operationId":"batchUpsertLaunches","summary":"Cutover-only batch overwrite","description":"Create-or-overwrite up to **100** skeletons by `public_id` / external id.\nNo per-row `If-Match`. Scope **`admin`**. Returns per-row results and\n`nextPromoteCursor` (last `public_id` written). Accepts `afterPublicId`\nto resume. **409** `{ \"error\": \"agent_live\" }` when\n`AGENT_WRITES_ENABLED=true` (live writer conflict, not a missing scope —\nwrong scope is still **403**).\n","x-scope":"admin","security":[{"BearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LaunchBatchRequest"}}}},"responses":{"200":{"description":"Per-row results plus promote cursor.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LaunchBatchResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/AgentLive"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/internal/launches/merge":{"post":{"tags":["Internal launches"],"operationId":"mergeLaunches","summary":"Merge loser into survivor","description":"Survivor UUID and `public_id` stay. Loser becomes a stub\n(`merged_into`). Scope **`admin`**. Response is **200** with\n`redirectTo` and the survivor payload.\n","x-scope":"admin","security":[{"BearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LaunchMergeRequest"}}}},"responses":{"200":{"description":"Survivor payload with redirectTo.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LaunchDetail"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/internal/launches/{id}":{"patch":{"tags":["Internal launches"],"operationId":"patchLaunch","summary":"Partial launch update","description":"Not PUT. Body is a partial draft and may include `visual` overrides.\nRequires `If-Match` = current `updated_at` (ISO) or `*` for first write.\n409 on mismatch. Scope `launches:write`.\n","x-scope":"launches:write","security":[{"BearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/LaunchId"},{"$ref":"#/components/parameters/IfMatch"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LaunchPatch"}}}},"responses":{"200":{"description":"Updated launch row.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Launch"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/PreconditionFailed"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/internal/launches/{id}/visual":{"patch":{"tags":["Internal launches"],"operationId":"patchLaunchVisual","summary":"Upsert visual binding","description":"Upsert `launch_visual_bindings` for this launch. Same `If-Match` rule\nas PATCH launch. Scope `launches:write`.\n","x-scope":"launches:write","security":[{"BearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/LaunchId"},{"$ref":"#/components/parameters/IfMatch"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"../schema/launch-draft.json#/$defs/visualBindingDraft"}}}},"responses":{"200":{"description":"Stored binding.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LaunchVisualBinding"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/PreconditionFailed"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/internal/launches/{id}/summary":{"put":{"tags":["Internal launches"],"operationId":"putLaunchSummary","summary":"Append a summary row","description":"Applies the `needs_review` / `is_current` rule. Agent writes with\n`status ∈ {failure, partial_failure, prelaunch_failure}` always insert\n`needs_review=true` and `is_current=false`. Scope `launches:write`.\n","x-scope":"launches:write","security":[{"BearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/LaunchId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"../schema/launch-draft.json#/$defs/summaryDraft"}}}},"responses":{"200":{"description":"Inserted summary row.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Summary"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/internal/launches/{id}/summary/{summaryId}":{"patch":{"tags":["Internal launches"],"operationId":"approveLaunchSummary","summary":"Approve a summary","description":"Sets `is_current=true`, `needs_review=false`. Scope **`admin`**.\n","x-scope":"admin","security":[{"BearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/LaunchId"},{"$ref":"#/components/parameters/SummaryId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SummaryApproveRequest"}}}},"responses":{"200":{"description":"Approved summary.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Summary"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/internal/launches/{id}/sources":{"put":{"tags":["Internal launches"],"operationId":"putLaunchSources","summary":"Merge source set","description":"Keyed by URL. Scope `launches:write`.","x-scope":"launches:write","security":[{"BearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/LaunchId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SourcesPutRequest"}}}},"responses":{"200":{"description":"Merged source set.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LaunchSourcesResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/internal/agent/runs":{"post":{"tags":["Internal agent"],"operationId":"startAgentRun","summary":"Start an agent run","description":"Returns `{ id, previousCursor }` from the last successful run.\nAgent never needs `/v1/admin`. Scope `launches:write`.\n","x-scope":"launches:write","security":[{"BearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false}}}},"responses":{"200":{"description":"New run id and the previous successful cursor.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartAgentRunResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"get":{"tags":["Internal agent"],"operationId":"listAgentRuns","summary":"List agent runs","description":"`?status=succeeded&limit=1` is the cursor read. Scope `launches:write`.\n","x-scope":"launches:write","security":[{"BearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/AgentRunStatusFilter"},{"$ref":"#/components/parameters/AgentRunLimit"}],"responses":{"200":{"description":"Run list.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentRunListResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/internal/agent/runs/{id}":{"patch":{"tags":["Internal agent"],"operationId":"patchAgentRun","summary":"Persist run cursor and status","description":"Write `cursor`, `counts`, `status=succeeded|failed`, timestamps.\nScope `launches:write`.\n","x-scope":"launches:write","security":[{"BearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/AgentRunId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentRunPatch"}}}},"responses":{"200":{"description":"Updated run.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentRun"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/internal/agent/invocations":{"post":{"tags":["Internal agent"],"operationId":"createPromptInvocation","summary":"Record one prompt invocation","description":"One `prompt_invocations` row. Scope `launches:write`.","x-scope":"launches:write","security":[{"BearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromptInvocationCreate"}}}},"responses":{"200":{"description":"Stored invocation.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromptInvocation"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/internal/maintenance/prune-usage":{"post":{"tags":["Internal maintenance"],"operationId":"pruneUsage","summary":"Prune usage events and idempotency keys","description":"Deletes `usage_events` with `occurred_at < now() - 90d` and expired\n`idempotency_keys`. Daily Agent calls this at the end of a successful\nrun. Scope `launches:write`.\n","x-scope":"launches:write","security":[{"BearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"description":"Delete counts.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PruneUsageResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/admin/keys":{"post":{"tags":["Admin keys"],"operationId":"createApiKey","summary":"Create an API key","description":"Returns the raw secret **once**. Persist only HMAC + 12-char prefix.\nScope **`admin`**.\n","x-scope":"admin","security":[{"BearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApiKeyRequest"}}}},"responses":{"200":{"description":"Key metadata plus one-time raw secret.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApiKeyResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"get":{"tags":["Admin keys"],"operationId":"listApiKeys","summary":"List API keys","description":"Prefixes, scopes, last used. Never `key_hash` or raw. Scope **`admin`**.","x-scope":"admin","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Key list without secrets.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyListResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/admin/keys/{id}/revoke":{"post":{"tags":["Admin keys"],"operationId":"revokeApiKey","summary":"Revoke an API key","description":"Sets `revoked_at`. Scope **`admin`**.","x-scope":"admin","security":[{"BearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/ApiKeyId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"description":"Revoked key metadata.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyPublic"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/admin/agent/runs":{"get":{"tags":["Admin agent"],"operationId":"adminListAgentRuns","summary":"Operator debug mirror of agent runs","description":"The Agent does not use this. Scope **`admin`**.","x-scope":"admin","security":[{"BearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/AgentRunStatusFilter"},{"$ref":"#/components/parameters/AgentRunLimit"}],"responses":{"200":{"description":"Run list.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentRunListResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}}},"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"sc_live_ / sc_test_","description":"`Authorization: Bearer sc_live_<22>` or `sc_test_<22>`.\nScopes live on the key row (`launches:read`, `launches:write`, `admin`).\nPer-operation `x-scope` is required; the tag is not an \"or\".\n"}},"headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer","minimum":0,"example":60}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer","minimum":0,"example":41}},"X-RateLimit-Reset":{"description":"Unix epoch seconds when the window resets.","schema":{"type":"integer","minimum":0,"example":1787000000}},"ETag":{"description":"Opaque validator derived from max(updated_at) of the index set.","schema":{"type":"string"}},"CacheControl":{"description":"Index cache hint (`public, max-age=60`).","schema":{"type":"string","example":"public, max-age=60"}}},"parameters":{"LaunchId":{"name":"id","in":"path","required":true,"description":"public_id or internal UUID. Merge losers resolve to the survivor.","schema":{"type":"string","minLength":1}},"FamilyId":{"name":"familyId","in":"path","required":true,"description":"visual-spec familyId slug (e.g. `falcon-9`).","schema":{"type":"string","minLength":1}},"SummaryId":{"name":"summaryId","in":"path","required":true,"schema":{"$ref":"../schema/common.json#/$defs/uuid"}},"ApiKeyId":{"name":"id","in":"path","required":true,"schema":{"$ref":"../schema/common.json#/$defs/uuid"}},"AgentRunId":{"name":"id","in":"path","required":true,"schema":{"$ref":"../schema/common.json#/$defs/uuid"}},"Class":{"name":"class","in":"query","required":false,"description":"Launch class filter. Omitted ⇒ `orbital`. Combined with `includeFeatured`\nas documented on the info block.\n","schema":{"$ref":"../schema/common.json#/$defs/launchClass"}},"IncludeFeatured":{"name":"includeFeatured","in":"query","required":false,"description":"Default false. When true with an explicit `class`, include `featured`\nrows of other classes. v1 clients do not send true.\n","schema":{"type":"boolean","default":false}},"FromDate":{"name":"from","in":"query","required":false,"description":"Inclusive lower bound on `net` (ISO date). Not `updated_at`.","schema":{"type":"string","format":"date"}},"ToDate":{"name":"to","in":"query","required":false,"description":"Exclusive upper bound on `net` (ISO date). Follow nextCursor until covered.","schema":{"type":"string","format":"date"}},"UpdatedSince":{"name":"updatedSince","in":"query","required":false,"description":"Filter `launches.updated_at >= updatedSince`. Agent incremental read.","schema":{"$ref":"../schema/common.json#/$defs/timestamptz"}},"LaunchStatus":{"name":"status","in":"query","required":false,"schema":{"$ref":"../schema/common.json#/$defs/launchStatus"}},"Q":{"name":"q","in":"query","required":false,"description":"Free-text name search. Names are not unique.","schema":{"type":"string","minLength":1}},"VehicleFamily":{"name":"vehicle_family","in":"query","required":false,"description":"visual-spec familyId slug.","schema":{"type":"string","minLength":1}},"Featured":{"name":"featured","in":"query","required":false,"description":"Catalog filter on `launches.featured`. Independent of the rail predicate.","schema":{"type":"boolean"}},"ExternalSystem":{"name":"externalSystem","in":"query","required":false,"description":"Must be paired with `externalValue`. Looks up `external_ids`.","schema":{"$ref":"../schema/common.json#/$defs/externalIdSystem"}},"ExternalValue":{"name":"externalValue","in":"query","required":false,"description":"Must be paired with `externalSystem`.","schema":{"type":"string","minLength":1}},"Cursor":{"name":"cursor","in":"query","required":false,"description":"Opaque page cursor from a previous `nextCursor`. Not a raw `net:id`.","schema":{"$ref":"#/components/schemas/OpaqueCursor"}},"Limit200":{"name":"limit","in":"query","required":false,"description":"Page size. Default 100, max 200. Never `offset`.","schema":{"type":"integer","minimum":1,"maximum":200,"default":100}},"AgentRunStatusFilter":{"name":"status","in":"query","required":false,"schema":{"$ref":"../schema/common.json#/$defs/agentRunStatus"}},"AgentRunLimit":{"name":"limit","in":"query","required":false,"description":"`limit=1` with `status=succeeded` is the previous-cursor read.","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},"IfMatch":{"name":"If-Match","in":"header","required":true,"description":"Current `updated_at` (ISO-8601) or `*` for first write.","schema":{"type":"string","minLength":1}},"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":false,"description":"Writer replay key. Stored 24h keyed by api_key_id + method + path.","schema":{"type":"string","minLength":1}}},"responses":{"BadRequest":{"description":"Invalid query combination or body.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Write route missing or unknown bearer key.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"Key is valid but lacks this route's scope (not an \"or\" with the section header).","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource does not exist, or this path is not mounted on the prod public\nlistener (`/v1/internal/*` and `/v1/admin/*` → 404, not 401).\n","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Conflict":{"description":"Conflicting state (duplicate merge, already-revoked key, …).","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"PreconditionFailed":{"description":"If-Match does not equal current `updated_at`.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"precondition_failed"}}}},"AgentLive":{"description":"`:batch` rejected because `AGENT_WRITES_ENABLED=true`. Pause the Agent\nMachine and flip the flag before re-promote. Wrong scope is 403, not this.\n","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentLiveError"},"example":{"error":"agent_live"}}}},"TooManyRequests":{"description":"Rate limit exceeded.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotReady":{"description":"Postgres is unreachable.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotReadyResponse"}}}}},"schemas":{"Launch":{"$ref":"../schema/launch.json"},"LaunchDraft":{"$ref":"../schema/launch-draft.json"},"LaunchMerge":{"$ref":"../schema/launch-merge.json"},"LaunchStage":{"$ref":"../schema/launch-stage.json"},"LaunchVisualBinding":{"$ref":"../schema/launch-visual-binding.json"},"PayloadGroup":{"$ref":"../schema/payload-group.json"},"BoosterRecovery":{"$ref":"../schema/booster-recovery.json"},"ExternalId":{"$ref":"../schema/external-id.json"},"Source":{"$ref":"../schema/source.json"},"Summary":{"$ref":"../schema/summary.json"},"Vehicle":{"$ref":"../schema/vehicle.json"},"VehicleConfig":{"$ref":"../schema/vehicle-config.json"},"StageTemplate":{"$ref":"../schema/stage-template.json"},"AgentRun":{"$ref":"../schema/agent-run.json"},"PromptInvocation":{"$ref":"../schema/prompt-invocation.json"},"ApiKey":{"$ref":"../schema/api-key.json"},"UsageEvent":{"$ref":"../schema/usage-event.json"},"IdempotencyKeyRecord":{"$ref":"../schema/idempotency-key.json"},"OpaqueCursor":{"type":"string","minLength":1,"description":"URL-safe base64 of a versioned `{ v, net, publicId }` payload. Clients must not parse it. Example is not `19571004T192834Z:1957-001`.\n","examples":["aXY0cXN3eY2lk"]},"Error":{"type":"object","additionalProperties":false,"required":["error"],"properties":{"error":{"type":"string","minLength":1},"message":{"type":"string"}}},"AgentLiveError":{"type":"object","additionalProperties":false,"required":["error"],"properties":{"error":{"type":"string","const":"agent_live"}}},"WindowEvent":{"type":"object","additionalProperties":false,"required":["event"],"properties":{"event":{"type":"string","enum":["play","watch","watch_complete"]},"publicId":{"type":"string","minLength":1,"maxLength":80,"description":"Launch public_id. Counted on `play` only."}}},"WindowFlightTally":{"type":"object","additionalProperties":false,"required":["publicId","count"],"properties":{"publicId":{"type":"string","minLength":1},"count":{"type":"integer","minimum":1}}},"WindowSnapshot":{"type":"object","additionalProperties":false,"required":["watchStarts","watchCompletes","plays","flights"],"properties":{"watchStarts":{"type":"integer","minimum":0},"watchCompletes":{"type":"integer","minimum":0},"plays":{"type":"integer","minimum":0},"flights":{"type":"array","maxItems":24,"items":{"$ref":"#/components/schemas/WindowFlightTally"}}}},"HealthResponse":{"type":"object","additionalProperties":false,"required":["status"],"properties":{"status":{"type":"string","const":"ok"}}},"NotReadyResponse":{"type":"object","additionalProperties":false,"required":["status"],"properties":{"status":{"type":"string","const":"not_ready"}}},"TimelineIndexFilter":{"type":"object","additionalProperties":false,"required":["class","includeFeatured"],"properties":{"class":{"$ref":"../schema/common.json#/$defs/launchClass"},"includeFeatured":{"type":"boolean"}}},"TimelineIndexItem":{"type":"object","additionalProperties":false,"required":["net","publicId","familyId","status","class","greyed","featured"],"properties":{"net":{"$ref":"../schema/common.json#/$defs/timestamptz"},"publicId":{"$ref":"../schema/common.json#/$defs/publicId"},"familyId":{"type":"string","minLength":1},"status":{"$ref":"../schema/common.json#/$defs/launchStatus"},"class":{"$ref":"../schema/common.json#/$defs/launchClass"},"greyed":{"type":"boolean","description":"Derived from status/net. Greyed ⇒ no takeoff."},"featured":{"type":"boolean"},"name":{"type":"string","minLength":1,"description":"Catalog display name. Required for Find aliases (jwst, hubble, …)."},"destination":{"type":"string","minLength":1,"description":"Primary payload destination (tli, l2, leo, …). Omitted when unknown."},"payloadName":{"type":"string","minLength":1},"launcher":{"type":"string","minLength":1,"description":"LL2 launcher label before visual-spec collapse."},"provider":{"type":"string","minLength":1,"description":"Launch service provider name."},"providerCountry":{"type":"string","minLength":2,"maxLength":2,"description":"ISO 3166-1 alpha-2 of the provider, not the pad."}}},"TimelineIndex":{"type":"object","additionalProperties":false,"required":["generatedAt","filter","count","items"],"properties":{"generatedAt":{"$ref":"../schema/common.json#/$defs/timestamptz"},"filter":{"$ref":"#/components/schemas/TimelineIndexFilter"},"count":{"type":"integer","minimum":0},"items":{"type":"array","items":{"$ref":"#/components/schemas/TimelineIndexItem"}}}},"TimelineCard":{"type":"object","additionalProperties":false,"required":["publicId","net","netPrecision","name","vehicleFamily","status","greyed","featured","hasAnimation","class"],"properties":{"publicId":{"$ref":"../schema/common.json#/$defs/publicId"},"net":{"$ref":"../schema/common.json#/$defs/timestamptz"},"netPrecision":{"$ref":"../schema/common.json#/$defs/netPrecision"},"name":{"type":"string","minLength":1},"vehicleFamily":{"type":"string","minLength":1},"lodAssetId":{"type":"string","minLength":1},"status":{"$ref":"../schema/common.json#/$defs/launchStatus"},"greyed":{"type":"boolean"},"featured":{"type":"boolean"},"hasAnimation":{"type":"boolean"},"class":{"$ref":"../schema/common.json#/$defs/launchClass"}}},"TimelinePage":{"type":"object","additionalProperties":false,"required":["items","from","to"],"properties":{"items":{"type":"array","maxItems":200,"items":{"$ref":"#/components/schemas/TimelineCard"}},"nextCursor":{"$ref":"#/components/schemas/OpaqueCursor"},"from":{"type":"string","format":"date"},"to":{"type":"string","format":"date"}}},"LaunchLookup":{"type":"object","additionalProperties":false,"description":"Timeline card plus writer keys (`id`, `publicId`, `externalIds`).","required":["id","publicId","externalIds","net","netPrecision","name","vehicleFamily","status","greyed","featured","hasAnimation","class"],"properties":{"id":{"$ref":"../schema/common.json#/$defs/uuid"},"publicId":{"$ref":"../schema/common.json#/$defs/publicId"},"net":{"$ref":"../schema/common.json#/$defs/timestamptz"},"netPrecision":{"$ref":"../schema/common.json#/$defs/netPrecision"},"name":{"type":"string","minLength":1},"vehicleFamily":{"type":"string","minLength":1},"lodAssetId":{"type":"string","minLength":1},"status":{"$ref":"../schema/common.json#/$defs/launchStatus"},"greyed":{"type":"boolean"},"featured":{"type":"boolean"},"hasAnimation":{"type":"boolean"},"class":{"$ref":"../schema/common.json#/$defs/launchClass"},"externalIds":{"type":"array","items":{"$ref":"#/components/schemas/ExternalId"}}}},"LaunchListResponse":{"type":"object","additionalProperties":false,"required":["items"],"properties":{"items":{"type":"array","maxItems":200,"items":{"$ref":"#/components/schemas/LaunchLookup"}},"nextCursor":{"$ref":"#/components/schemas/OpaqueCursor"}}},"LaunchDetail":{"type":"object","additionalProperties":false,"required":["id","public_id","class","status","featured","net","net_precision","updated_at","name","greyed"],"properties":{"id":{"$ref":"../schema/common.json#/$defs/uuid"},"public_id":{"$ref":"../schema/common.json#/$defs/publicId"},"cospar_series":{"type":["string","null"]},"class":{"$ref":"../schema/common.json#/$defs/launchClass"},"status":{"$ref":"../schema/common.json#/$defs/launchStatus"},"featured":{"type":"boolean"},"net":{"$ref":"../schema/common.json#/$defs/timestamptz"},"net_precision":{"$ref":"../schema/common.json#/$defs/netPrecision"},"updated_at":{"$ref":"../schema/common.json#/$defs/timestamptz"},"vehicle_config_id":{"oneOf":[{"$ref":"../schema/common.json#/$defs/uuid"},{"type":"null"}]},"name":{"type":"string","minLength":1},"merged_into":{"oneOf":[{"$ref":"../schema/common.json#/$defs/uuid"},{"type":"null"}]},"greyed":{"type":"boolean","description":"Derived. Not stored."},"redirectTo":{"$ref":"../schema/common.json#/$defs/publicId","description":"Survivor public_id when `{id}` was a merge loser. 200, not 301."},"stages":{"type":"array","items":{"$ref":"#/components/schemas/LaunchStage"}},"payload_groups":{"type":"array","items":{"$ref":"#/components/schemas/PayloadGroup"}},"booster_recoveries":{"type":"array","items":{"$ref":"#/components/schemas/BoosterRecovery"}},"external_ids":{"type":"array","items":{"$ref":"#/components/schemas/ExternalId"}}}},"LaunchSourcesResponse":{"type":"object","additionalProperties":false,"required":["items"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Source"}}}},"LaunchSummaryResponse":{"type":"object","additionalProperties":false,"required":["pendingReview"],"properties":{"summary":{"oneOf":[{"$ref":"#/components/schemas/Summary"},{"type":"null"}]},"pendingReview":{"type":"boolean","description":"True when a newer needs_review row exists."}}},"LaunchVisualResponse":{"type":"object","additionalProperties":false,"required":["familyId","recoveries","family"],"properties":{"familyId":{"type":"string","minLength":1},"configId":{"type":"string","minLength":1},"binding":{"oneOf":[{"$ref":"#/components/schemas/LaunchVisualBinding"},{"type":"null"}],"description":"Null when no launch_visual_bindings row exists (`_generic`)."},"recoveries":{"type":"array","items":{"$ref":"#/components/schemas/BoosterRecovery"}},"family":{"type":"object","additionalProperties":true,"description":"Generated visual-spec JSON from packages/visual-spec/gen. No SVG."}}},"VehicleListResponse":{"type":"object","additionalProperties":false,"required":["items"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/VehicleFamilySummary"}}}},"VehicleFamilySummary":{"type":"object","additionalProperties":false,"required":["id","family_id","display_name","configs"],"properties":{"id":{"$ref":"../schema/common.json#/$defs/uuid"},"family_id":{"type":"string","minLength":1},"display_name":{"type":"string","minLength":1},"updated_at":{"$ref":"../schema/common.json#/$defs/timestamptz"},"configs":{"type":"array","items":{"$ref":"#/components/schemas/VehicleConfig"}}}},"VehicleFamilyDetail":{"type":"object","additionalProperties":false,"required":["id","family_id","display_name","configs"],"properties":{"id":{"$ref":"../schema/common.json#/$defs/uuid"},"family_id":{"type":"string","minLength":1},"display_name":{"type":"string","minLength":1},"updated_at":{"$ref":"../schema/common.json#/$defs/timestamptz"},"configs":{"type":"array","items":{"$ref":"#/components/schemas/VehicleConfigDetail"}}}},"VehicleConfigDetail":{"type":"object","additionalProperties":false,"required":["id","vehicle_id","slug","display_name","stage_templates"],"properties":{"id":{"$ref":"../schema/common.json#/$defs/uuid"},"vehicle_id":{"$ref":"../schema/common.json#/$defs/uuid"},"slug":{"type":"string","minLength":1},"display_name":{"type":"string","minLength":1},"updated_at":{"$ref":"../schema/common.json#/$defs/timestamptz"},"stage_templates":{"type":"array","items":{"$ref":"#/components/schemas/StageTemplate"}}}},"LaunchPatch":{"type":"object","additionalProperties":false,"description":"Partial LaunchDraft. Not PUT. May include visual overrides.","properties":{"cospar_series":{"type":["string","null"]},"class":{"$ref":"../schema/common.json#/$defs/launchClass"},"status":{"$ref":"../schema/common.json#/$defs/launchStatus"},"featured":{"type":"boolean"},"net":{"$ref":"../schema/common.json#/$defs/timestamptz"},"net_precision":{"$ref":"../schema/common.json#/$defs/netPrecision"},"name":{"type":"string","minLength":1},"vehicle_config_id":{"oneOf":[{"$ref":"../schema/common.json#/$defs/uuid"},{"type":"null"}]},"vehicle_family_id":{"type":["string","null"]},"vehicle_config_slug":{"type":["string","null"]},"external_ids":{"$ref":"../schema/launch-draft.json#/properties/external_ids"},"stages":{"$ref":"../schema/launch-draft.json#/properties/stages"},"payload_groups":{"$ref":"../schema/launch-draft.json#/properties/payload_groups"},"booster_recoveries":{"$ref":"../schema/launch-draft.json#/properties/booster_recoveries"},"sources":{"$ref":"../schema/launch-draft.json#/properties/sources"},"summary":{"$ref":"../schema/launch-draft.json#/properties/summary"},"visual":{"$ref":"../schema/launch-draft.json#/properties/visual"}}},"LaunchBatchRequest":{"type":"object","additionalProperties":false,"required":["items"],"properties":{"items":{"type":"array","minItems":1,"maxItems":100,"items":{"$ref":"#/components/schemas/LaunchDraft"}},"afterPublicId":{"$ref":"../schema/common.json#/$defs/publicId","description":"Resume promote after this public_id."}}},"LaunchBatchRowResult":{"type":"object","additionalProperties":false,"required":["public_id","ok"],"properties":{"public_id":{"$ref":"../schema/common.json#/$defs/publicId"},"ok":{"type":"boolean"},"id":{"$ref":"../schema/common.json#/$defs/uuid"},"error":{"type":"string"}}},"LaunchBatchResponse":{"type":"object","additionalProperties":false,"required":["results"],"properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/LaunchBatchRowResult"}},"nextPromoteCursor":{"type":["string","null"],"description":"Last public_id written. Resume with afterPublicId."}}},"LaunchMergeRequest":{"type":"object","additionalProperties":false,"required":["survivorId","loserId"],"properties":{"survivorId":{"$ref":"../schema/common.json#/$defs/uuid"},"loserId":{"$ref":"../schema/common.json#/$defs/uuid"},"note":{"type":["string","null"]}}},"SummaryApproveRequest":{"type":"object","additionalProperties":false,"required":["is_current","needs_review"],"properties":{"is_current":{"type":"boolean","const":true},"needs_review":{"type":"boolean","const":false}}},"SourcesPutRequest":{"type":"object","additionalProperties":false,"required":["items"],"properties":{"items":{"type":"array","items":{"$ref":"../schema/launch-draft.json#/$defs/sourceDraft"}}}},"StartAgentRunResponse":{"type":"object","additionalProperties":false,"required":["id","previousCursor"],"properties":{"id":{"$ref":"../schema/common.json#/$defs/uuid"},"previousCursor":{"type":["string","null"],"description":"Cursor from the last successful run."}}},"AgentRunListResponse":{"type":"object","additionalProperties":false,"required":["items"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/AgentRun"}}}},"AgentRunPatch":{"type":"object","additionalProperties":false,"properties":{"status":{"$ref":"../schema/common.json#/$defs/agentRunStatus"},"cursor":{"type":["string","null"]},"counts":{"$ref":"../schema/agent-run.json#/properties/counts"},"finished_at":{"$ref":"../schema/common.json#/$defs/timestamptz"},"error":{"type":["string","null"]}}},"PromptInvocationCreate":{"type":"object","additionalProperties":false,"required":["run_id","tag","prompt_hash","model","tokens_in","tokens_out","ok"],"properties":{"run_id":{"$ref":"../schema/common.json#/$defs/uuid"},"launch_id":{"oneOf":[{"$ref":"../schema/common.json#/$defs/uuid"},{"type":"null"}]},"tag":{"type":"string","minLength":1},"prompt_hash":{"type":"string","minLength":1},"model":{"type":"string","minLength":1},"tokens_in":{"type":"integer","minimum":0},"tokens_out":{"type":"integer","minimum":0},"ok":{"type":"boolean"},"error":{"type":["string","null"]}}},"PruneUsageResponse":{"type":"object","additionalProperties":false,"required":["usageEventsDeleted","idempotencyKeysDeleted"],"properties":{"usageEventsDeleted":{"type":"integer","minimum":0},"idempotencyKeysDeleted":{"type":"integer","minimum":0}}},"CreateApiKeyRequest":{"type":"object","additionalProperties":false,"required":["name","scopes"],"properties":{"name":{"type":"string","minLength":1},"scopes":{"type":"array","minItems":1,"uniqueItems":true,"items":{"$ref":"../schema/common.json#/$defs/apiKeyScope"}},"kind":{"$ref":"../schema/common.json#/$defs/apiKeyKind"},"owner_label":{"type":["string","null"]}}},"ApiKeyPublic":{"type":"object","additionalProperties":false,"required":["id","prefix","kind","scopes","name","created_at"],"description":"List/revoke shape. Never includes key_hash or the raw secret.","properties":{"id":{"$ref":"../schema/common.json#/$defs/uuid"},"prefix":{"type":"string","minLength":12,"maxLength":12},"kind":{"$ref":"../schema/common.json#/$defs/apiKeyKind"},"scopes":{"type":"array","minItems":1,"uniqueItems":true,"items":{"$ref":"../schema/common.json#/$defs/apiKeyScope"}},"name":{"type":"string","minLength":1},"created_at":{"$ref":"../schema/common.json#/$defs/timestamptz"},"last_used_at":{"oneOf":[{"$ref":"../schema/common.json#/$defs/timestamptz"},{"type":"null"}]},"revoked_at":{"oneOf":[{"$ref":"../schema/common.json#/$defs/timestamptz"},{"type":"null"}]},"owner_label":{"type":["string","null"]}}},"CreateApiKeyResponse":{"type":"object","additionalProperties":false,"required":["id","prefix","kind","scopes","name","created_at","raw"],"properties":{"id":{"$ref":"../schema/common.json#/$defs/uuid"},"prefix":{"type":"string","minLength":12,"maxLength":12},"kind":{"$ref":"../schema/common.json#/$defs/apiKeyKind"},"scopes":{"type":"array","minItems":1,"uniqueItems":true,"items":{"$ref":"../schema/common.json#/$defs/apiKeyScope"}},"name":{"type":"string","minLength":1},"created_at":{"$ref":"../schema/common.json#/$defs/timestamptz"},"last_used_at":{"oneOf":[{"$ref":"../schema/common.json#/$defs/timestamptz"},{"type":"null"}]},"revoked_at":{"oneOf":[{"$ref":"../schema/common.json#/$defs/timestamptz"},{"type":"null"}]},"owner_label":{"type":["string","null"]},"raw":{"type":"string","minLength":1,"description":"Full secret, shown once. `sc_live_` / `sc_test_` + 22 url-safe chars."}}},"ApiKeyListResponse":{"type":"object","additionalProperties":false,"required":["items"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ApiKeyPublic"}}}}}}}