{"openapi":"3.1.0","info":{"title":"LoopKit API","version":"0.0.0","description":"Agent-first improvement loops over production telemetry. Producers write OTLP spans; agents run diagnosis and read bounded evidence windows. External writes are human-approved."},"servers":[{"url":"https://loopops.dev"}],"components":{"securitySchemes":{"producerKey":{"type":"http","scheme":"bearer","description":"Producer key with telemetry:write scope."},"agentKey":{"type":"http","scheme":"bearer","description":"Agent key with runs:write, telemetry:read, dashboard:read, reports:read scopes."}},"schemas":{"DiagnosisResponse":{"type":"object","required":["ok","packet"],"properties":{"ok":{"type":"boolean"},"packet":{"$ref":"#/components/schemas/DiagnosisPacket"}}},"DiagnosisPacket":{"type":"object","description":"Bounded-window diagnosis result. Note: this is the worker's runtime diagnosis packet and differs from the CLI run manifest at /schema/improvement-packet.json.","required":["packet_id","tenant_id","project_id","status","issues","approval_required","created_at"],"properties":{"packet_id":{"type":"string"},"tenant_id":{"type":"string"},"project_id":{"type":"string"},"source":{"type":"string"},"status":{"type":"string","enum":["ready_for_issue_diagnosis","observe_more"]},"since_minutes":{"type":"integer"},"telemetry_read":{"type":"integer"},"failures":{"type":"integer"},"approval_required":{"type":"boolean"},"approval_reason":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"issues":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string"},"severity":{"type":"string"},"signal_type":{"type":"string"},"count":{"type":"integer"},"evidence_ids":{"type":"array","items":{"type":"string"}},"traces":{"type":"array","items":{"type":"string"}},"next_action":{"type":"string"}}}}}}}},"paths":{"/v1/provision":{"post":{"summary":"Idempotent onboarding: public mint, authenticated non-rotating resume, auto-graduate","description":"One re-runnable call that converges to the same project for a given name. MINT (name unclaimed): public, rate-limited, race-guarded; returns fresh producer + agent keys, self-graduated to a permanent key when PROVISION_AUTO_GRADUATE is on and SIGNUP_UPGRADE_SECRET is set. RESUME (name maps to a live project): requires Authorization: Bearer with one of THAT project's own keys; it is NON-ROTATING — the project is graduated in place and no new key is issued and nothing is deleted (the caller keeps the key it authenticated with). Resuming without the owning key is rejected (401/403). Raw keys are never stored at rest. Response is the signup shape plus resumed and graduated booleans.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["project_name"],"properties":{"project_name":{"type":"string","description":"Idempotency key — re-running with the same name converges to the same project."},"operator":{"type":"string","description":"Optional namespace so two operators can each own the same project name in the shared sandbox tenant."}}}}}},"responses":{"200":{"description":"Resumed an existing project in place (authenticated, non-rotating)"},"201":{"description":"Minted a new project"},"400":{"description":"project_name is required"},"401":{"description":"Resume requires the project's producer or agent key"},"403":{"description":"The provided key does not authorize this project"},"409":{"description":"Name concurrently claimed by another mint; resume with the owning key"},"429":{"description":"Provision rate limit reached (mint path only)"}}}},"/v1/recover":{"post":{"summary":"Rotate lost keys with a recovery code","description":"Recover access when the keys are lost. Gated by an unforgeable, reproducible recovery code = HMAC-SHA256(\"recover:<project_id>\") signed with SIGNUP_UPGRADE_SECRET (returned at signup/provision, never stored). On a valid code it mints fresh producer + agent keys, repoints the project token index, and revokes the old keys. Sandbox-tenant only; raw keys are never stored at rest; the recovery_code does not change when keys rotate. Humans can also recover in the browser at /dashboard.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["project_id","recovery_code"],"properties":{"project_id":{"type":"string","description":"The project to recover."},"recovery_code":{"type":"string","description":"HMAC-SHA256 hex for recover:<project_id>, from your signup/provision response."}}}}}},"responses":{"200":{"description":"Keys rotated; returns fresh producer_key, agent_key, and a dashboard_url"},"400":{"description":"project_id and recovery_code are required"},"403":{"description":"Recovery code verification failed"},"404":{"description":"No such project"},"429":{"description":"Recovery rate limit reached for your network"},"503":{"description":"Recovery unavailable (TOKEN_KV or SIGNUP_UPGRADE_SECRET absent)"}}}},"/v1/signup/upgrade":{"post":{"summary":"Upgrade a sandbox signup to permanent-but-capped keys","description":"Requires an active sandbox agent key plus a project-scoped HMAC code signed with SIGNUP_UPGRADE_SECRET. Removes expires_at and plan from both producer and agent token records while preserving quota_spans.","security":[{"agentKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string","description":"HMAC-SHA256 hex for signup-upgrade:sandbox:<project_id>."},"producer_key":{"type":"string","description":"Fallback only: producer bearer key for pre-index sandboxes."},"agent_key":{"type":"string","description":"Fallback only: agent bearer key for pre-index sandboxes."}}}}}},"responses":{"200":{"description":"Principal summary; no raw secrets returned"},"401":{"description":"Agent authorization required"},"403":{"description":"Upgrade code verification failed"}}}},"/v1/telemetry":{"get":{"summary":"Read the recent scoped telemetry record window","security":[{"agentKey":[]}],"responses":{"200":{"description":"Recent telemetry records"},"401":{"description":"Authorization required"}}}},"/v1/sessions":{"get":{"summary":"List sessions in a bounded window (agent / outcome / turns / timing)","security":[{"agentKey":[]}],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":500,"default":50},"description":"Max sessions returned after grouping"},{"name":"since_minutes","in":"query","schema":{"type":"integer","minimum":1,"maximum":43200}},{"name":"environment","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Session summaries, most recent first"},"401":{"description":"Authorization required"}}}},"/v1/sessions/{id}":{"get":{"summary":"Read one session: its summary plus the raw, time-ordered telemetry records (turns)","security":[{"agentKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"A session_id or a trace_id"},{"name":"since_minutes","in":"query","schema":{"type":"integer","minimum":1,"maximum":43200}}],"responses":{"200":{"description":"Session detail with raw records"},"401":{"description":"Authorization required"},"404":{"description":"Session not found in window"}}}},"/v1/traces":{"post":{"summary":"Ingest OTLP JSON spans as LoopKit telemetry records","security":[{"producerKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["resourceSpans"],"properties":{"resourceSpans":{"type":"array"}}}}}},"responses":{"202":{"description":"Accepted"},"400":{"description":"Invalid OTLP JSON body"},"401":{"description":"Producer authorization required"}}}},"/v1/runs/diagnose":{"post":{"summary":"Run a diagnosis over a bounded window and return a diagnosis packet","security":[{"agentKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"since_minutes":{"type":"integer","minimum":1,"maximum":43200,"default":60}}}}}},"responses":{"200":{"description":"Diagnosis packet wrapper","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DiagnosisResponse"}}}},"401":{"description":"Agent authorization required"}}}},"/v1/dashboard":{"get":{"summary":"Agent-readable dashboard JSON","security":[{"agentKey":[]}],"responses":{"200":{"description":"Dashboard"}}}},"/v1/reports/latest.md":{"get":{"summary":"Latest telemetry report as markdown","security":[{"agentKey":[]}],"responses":{"200":{"description":"Markdown report"}}}},"/health":{"get":{"summary":"Liveness","responses":{"200":{"description":"OK"}}}}}}