{"openapi":"3.1.0","info":{"title":"OmicOS Server","version":"0.1.0"},"paths":{"/admin/api/public/models":{"get":{"summary":"Public Model Catalog","operationId":"public_model_catalog_admin_api_public_models_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Public Model Catalog Admin Api Public Models Get"}}}}}}},"/admin/api/public/models/manifest":{"get":{"summary":"Public Model Catalog Manifest","operationId":"public_model_catalog_manifest_admin_api_public_models_manifest_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Public Model Catalog Manifest Admin Api Public Models Manifest Get"}}}}}}},"/admin/api/public/omicos/{product}/manifest":{"get":{"summary":"Public Release Manifest","description":"Latest-version manifest for `product` ∈ {desktop, core}.\n\nPublic + unauthenticated — desktop/CLI clients poll it on a timer.\nWhen no manifest has been published yet we return 204 No Content,\nwhich Tauri's updater treats as \"no update available\" (and the CLI\nhandles the same way) — better than a 404 the client has to\nspecial-case.","operationId":"public_release_manifest_admin_api_public_omicos__product__manifest_get","parameters":[{"name":"product","in":"path","required":true,"schema":{"type":"string","title":"Product"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/api/release/{product}/manifest":{"put":{"summary":"Put Release Manifest","description":"Publish a new manifest for `product`. Called by the release CI\nafter it has uploaded the binaries to Cloudflare R2.\n\nAuth: `Authorization: Bearer <OMICOS_RELEASE_TOKEN>`. If that env\nvar is unset the endpoint refuses everything — there must never be\nan unauthenticated write path for \"what version should everyone\nupdate to\".","operationId":"put_release_manifest_admin_api_release__product__manifest_put","parameters":[{"name":"product","in":"path","required":true,"schema":{"type":"string","title":"Product"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReleaseManifestIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Put Release Manifest Admin Api Release  Product  Manifest Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/api/public/omicos/{product}/download/{version}/{filename}":{"get":{"summary":"Public Release Asset","description":"Reverse-proxy + on-disk cache for a release binary.\n\nClients are pointed here by the manifest (same host as the manifest)\ninstead of straight at Cloudflare R2, so a client that can reach the\nmanifest can always reach the binary. Public + unauthenticated, like\nthe manifest read. Bytes are served verbatim — the client verifies its\nown SHA-256 (core) / signature (desktop shell).\n\nServes byte ranges. This is not a nicety: the CN edge in front of this\nendpoint fetches large objects in slices, and an origin that answers\nevery slice with the entire file can never populate a slice cache — so\nit degraded to fetching all ~30 MB from this box for every client, over\nan uplink that is already at its cap. It is also what lets a client\nresume a transfer the link dropped instead of restarting from zero.","operationId":"public_release_asset_admin_api_public_omicos__product__download__version___filename__get","parameters":[{"name":"product","in":"path","required":true,"schema":{"type":"string","title":"Product"}},{"name":"version","in":"path","required":true,"schema":{"type":"string","title":"Version"}},{"name":"filename","in":"path","required":true,"schema":{"type":"string","title":"Filename"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/labs":{"post":{"tags":["labs"],"summary":"Create Lab","operationId":"create_lab_api_labs_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateLabIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Create Lab Api Labs Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["labs"],"summary":"List My Labs","description":"Every lab the user is in, with their role inside. Owner labs include\nthe invitation code so the dashboard can render it; member labs hide it\n(members shouldn't be able to re-share).","operationId":"list_my_labs_api_labs_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response List My Labs Api Labs Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/labs/preview":{"get":{"tags":["labs"],"summary":"Preview Team Code","description":"Return ONLY the team NAME a code points at, so a joiner can confirm which\nteam they're requesting to join before submitting (POST /join). Grants\nnothing and reveals nothing else — no owner, members, or seat counts; the\nowner-approval gate still governs actual membership. Any invalid / expired /\ninactive code returns a uniform 404 so probing a wrong code learns nothing.\n\nMUST stay defined BEFORE `/{lab_id}` so the static path wins the route match\n(FastAPI matches in definition order).","operationId":"preview_team_code_api_labs_preview_get","parameters":[{"name":"code","in":"query","required":true,"schema":{"type":"string","title":"Code"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Preview Team Code Api Labs Preview Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/labs/{lab_id}":{"get":{"tags":["labs"],"summary":"Get Lab","operationId":"get_lab_api_labs__lab_id__get","parameters":[{"name":"lab_id","in":"path","required":true,"schema":{"type":"string","title":"Lab Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get Lab Api Labs  Lab Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/labs/{lab_id}/usage":{"get":{"tags":["labs"],"summary":"Lab Usage","description":"Owner-only team usage dashboard: per-member token aggregates + team\ntotals. Aggregate COUNTS ONLY (tokens, sessions, last-active) — never\ntrajectory content, which stays private to each member. This is the extra\nvalue the team owner gets for paying per seat: see who's actually using it.","operationId":"lab_usage_api_labs__lab_id__usage_get","parameters":[{"name":"lab_id","in":"path","required":true,"schema":{"type":"string","title":"Lab Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Lab Usage Api Labs  Lab Id  Usage Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/labs/{lab_id}/board":{"get":{"tags":["labs"],"summary":"Lab Board","description":"Everything the team page needs about the team's SHARED analyses, in one\nrequest: what each run did, where it stopped, whether it failed, what it\nproduced, how many figures it made, and which comments are still open.\n\nSupersedes `/reviews` (comments only). The PI-facing board groups these\ninto 跑挂了 / 等你回应 / 停滞 / 最近完成, and every one of those groups needs\na different field off the same scan — splitting it across endpoints would\nmean two passes over the same sidecar files.\n\nThe client does the grouping: the thresholds (\"stale after 3 days\") are\npresentation policy and will get tuned, and re-deploying the server to\nchange a cutoff would be the wrong coupling.","operationId":"lab_board_api_labs__lab_id__board_get","parameters":[{"name":"lab_id","in":"path","required":true,"schema":{"type":"string","title":"Lab Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"how many shared sessions to scan","default":200,"title":"Limit"},"description":"how many shared sessions to scan"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Lab Board Api Labs  Lab Id  Board Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/labs/{lab_id}/reviews":{"get":{"tags":["labs"],"summary":"Lab Reviews","description":"Open review threads across the team's shared trajectories.\n\nThis is the team-level view the per-session annotations endpoints cannot\ngive: a member sees, in one place, which comments are waiting on THEM\n(`for_me` — on a trajectory they own) versus what they have asked of\nothers (`by_me`). Without it, an annotation is only discoverable by\nwhoever happens to reopen that exact trajectory, which is why the review\nprimitives shipped but nobody used them.\n\nVisible to every ACTIVE member (not owner-only): peer review is the point.\nScope matches `visibility='lab'` — a private trajectory is never scanned,\nso annotating one is still a private note between owner and viewer.","operationId":"lab_reviews_api_labs__lab_id__reviews_get","parameters":[{"name":"lab_id","in":"path","required":true,"schema":{"type":"string","title":"Lab Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"how many shared sessions to scan","default":200,"title":"Limit"},"description":"how many shared sessions to scan"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Lab Reviews Api Labs  Lab Id  Reviews Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/labs/{lab_id}/rotate_code":{"post":{"tags":["labs"],"summary":"Rotate Invitation Code","description":"Owner-only. Replaces the invitation code immediately (the old one\nstops working on the next request) and resets the 30-day clock.","operationId":"rotate_invitation_code_api_labs__lab_id__rotate_code_post","parameters":[{"name":"lab_id","in":"path","required":true,"schema":{"type":"string","title":"Lab Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Rotate Invitation Code Api Labs  Lab Id  Rotate Code Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/labs/join":{"post":{"tags":["labs"],"summary":"Join Lab","operationId":"join_lab_api_labs_join_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JoinLabIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Join Lab Api Labs Join Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/labs/{lab_id}/membership":{"delete":{"tags":["labs"],"summary":"Leave Lab","description":"Member-initiated departure. Sets status='left' rather than deleting\nthe row so audit / past trajectory access is preserved.","operationId":"leave_lab_api_labs__lab_id__membership_delete","parameters":[{"name":"lab_id","in":"path","required":true,"schema":{"type":"string","title":"Lab Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Leave Lab Api Labs  Lab Id  Membership Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/labs/{lab_id}/members/{member_user_id}":{"patch":{"tags":["labs"],"summary":"Update Member","description":"Owner-only. Approve/reject a pending join request, promote/demote between\nmentor/member, or revoke. `status='active'` on a pending member APPROVES it\n(grants the seat + Lab tier, subject to seat quota); `status='rejected'`\ndeclines it. Owner cannot modify their own membership.","operationId":"update_member_api_labs__lab_id__members__member_user_id__patch","parameters":[{"name":"lab_id","in":"path","required":true,"schema":{"type":"string","title":"Lab Id"}},{"name":"member_user_id","in":"path","required":true,"schema":{"type":"string","title":"Member User Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMemberIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Update Member Api Labs  Lab Id  Members  Member User Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/luria/plans":{"post":{"tags":["luria"],"summary":"Create Plan","description":"Draft a plan. Charges the TOKEN line only — no sandbox is running, so\nthere is nothing to bill on compute. Charging at all is what makes plan\nspam pointless; it replaces a rate limiter with an honest price.","operationId":"create_plan_api_luria_plans_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Create Plan Api Luria Plans Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/luria/plans/current":{"get":{"tags":["luria"],"summary":"Current Plan","description":"The draft still awaiting a decision, if any. The SPA calls this on\nload: the user already paid for that plan, so a page reload must not\nmake them buy the same answer twice.","operationId":"current_plan_api_luria_plans_current_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Current Plan Api Luria Plans Current Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/luria/plans/{plan_id}/approve":{"post":{"tags":["luria"],"summary":"Approve Plan","description":"The approval. THIS is what claims compute and starts the meter —\nopening the app does not, and drafting a plan does not.","operationId":"approve_plan_api_luria_plans__plan_id__approve_post","parameters":[{"name":"plan_id","in":"path","required":true,"schema":{"type":"integer","title":"Plan Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Approve Plan Api Luria Plans  Plan Id  Approve Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/luria/plans/{plan_id}/decline":{"post":{"tags":["luria"],"summary":"Decline Plan","description":"Drop a plan without running it. The generation charge stands — the\nplanner did the work — which is why the SPA states that cost on the\nbutton that spends it rather than after the fact.","operationId":"decline_plan_api_luria_plans__plan_id__decline_post","parameters":[{"name":"plan_id","in":"path","required":true,"schema":{"type":"integer","title":"Plan Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Decline Plan Api Luria Plans  Plan Id  Decline Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/luria/sessions/{lease_id}/topup":{"post":{"tags":["luria"],"summary":"Topup Session","description":"Raise a paused lease's ceiling and let the run continue.\n\nDeclining to top up costs the user nothing beyond what is already spent:\nthe pause happens with the work checkpointed, so ending here keeps every\nresult. That is what makes this a guardrail rather than a ransom note.","operationId":"topup_session_api_luria_sessions__lease_id__topup_post","parameters":[{"name":"lease_id","in":"path","required":true,"schema":{"type":"integer","title":"Lease Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TopUpIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Topup Session Api Luria Sessions  Lease Id  Topup Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/luria/sessions":{"post":{"tags":["luria"],"summary":"Create Session","description":"Claim a sandbox directly, WITHOUT an approved plan.\n\nThis predates the approval gate and is kept for the manual-provider dev\nflow and the test suite. The SPA no longer calls it — it goes through\n/plans → /plans/{id}/approve, which is what enforces \"the user saw a\nquote and said yes\" and what freezes a spend ceiling onto the lease. A\nlease made here has no ceiling, so the budget guardrail skips it.\n\nWorth closing off once nothing but tests depends on it; leaving it open\nmeans the approval gate is a UI convention rather than a server one.","operationId":"create_session_api_luria_sessions_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Create Session Api Luria Sessions Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/luria/sessions/current":{"get":{"tags":["luria"],"summary":"Current Session","operationId":"current_session_api_luria_sessions_current_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Current Session Api Luria Sessions Current Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/luria/sessions/{lease_id}":{"get":{"tags":["luria"],"summary":"Get Session","operationId":"get_session_api_luria_sessions__lease_id__get","parameters":[{"name":"lease_id","in":"path","required":true,"schema":{"type":"integer","title":"Lease Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get Session Api Luria Sessions  Lease Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["luria"],"summary":"Release Session","operationId":"release_session_api_luria_sessions__lease_id__delete","parameters":[{"name":"lease_id","in":"path","required":true,"schema":{"type":"integer","title":"Lease Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Release Session Api Luria Sessions  Lease Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/luria/sessions/{lease_id}/heartbeat":{"post":{"tags":["luria"],"summary":"Heartbeat","description":"Called by luria-core inside the sandbox (process token) every ~60s.\n`active=true` means a turn ran recently — it refreshes the idle clock.\nReturns balance + state so the core can surface 「点数即将用尽」to the\nconversation before the drain hits.","operationId":"heartbeat_api_luria_sessions__lease_id__heartbeat_post","parameters":[{"name":"lease_id","in":"path","required":true,"schema":{"type":"integer","title":"Lease Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HeartbeatIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Heartbeat Api Luria Sessions  Lease Id  Heartbeat Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/luria/bootstrap":{"post":{"tags":["luria"],"summary":"Bootstrap","description":"Sandbox-side: exchange the single-use claim token for the token pair\nluria-core's cloud_login.json expects. Mirrors /api/auth/cli-poll's\napproved response so the core's existing plumbing works unchanged; the\nuser token is short-TTL (sandbox lifetime is bounded by credits anyway).\n\nUnauthenticated by design — the claim token IS the credential. Atomic\nsingle-use: the UPDATE both consumes the hash and flips the lease to\n`ready`, so a replayed token finds no row.","operationId":"bootstrap_api_luria_bootstrap_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BootstrapIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Bootstrap Api Luria Bootstrap Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/luria/credits":{"get":{"tags":["luria"],"summary":"Credits","operationId":"credits_api_luria_credits_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Credits Api Luria Credits Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/trajectories":{"get":{"tags":["trajectories"],"summary":"List Trajectories","operationId":"list_trajectories_api_trajectories_get","parameters":[{"name":"owner","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"filter to a specific owner; mentor-only","title":"Owner"},"description":"filter to a specific owner; mentor-only"},{"name":"lab_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"filter to a specific lab","title":"Lab Id"},"description":"filter to a specific lab"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"include_archived","in":"query","required":false,"schema":{"type":"integer","description":"include archived (locally-deleted) sessions","default":0,"title":"Include Archived"},"description":"include archived (locally-deleted) sessions"},{"name":"shared_only","in":"query","required":false,"schema":{"type":"integer","description":"only visibility='lab' rows (the team's shared wall)","default":0,"title":"Shared Only"},"description":"only visibility='lab' rows (the team's shared wall)"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response List Trajectories Api Trajectories Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/trajectories/{session_id}":{"get":{"tags":["trajectories"],"summary":"Get Full Trajectory","operationId":"get_full_trajectory_api_trajectories__session_id__get","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"if set, return only records after this uuid","title":"Since"},"description":"if set, return only records after this uuid"},{"name":"download","in":"query","required":false,"schema":{"type":"boolean","description":"serve as application/x-ndjson attachment","default":false,"title":"Download"},"description":"serve as application/x-ndjson attachment"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["trajectories"],"summary":"Delete Trajectory","operationId":"delete_trajectory_api_trajectories__session_id__delete","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Delete Trajectory Api Trajectories  Session Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/trajectories/{session_id}/figures":{"post":{"tags":["trajectories"],"summary":"Publish Figure Urls","description":"Record {canonical local figure path → figure blob refs} mappings for a\nsession. omicos-core POSTs this after it background-uploads a figure, so the\nserver can rewrite the local path → a serve url when it serves the\ntrajectory to a cloud client (see get_full_trajectory). The stored\ntrajectory bytes are never touched — only this side table.\n\nBody: {\"figures\": [{\n    \"canonical_path\",\n    \"cdn_url\"?,        # legacy public-figs CDN url\n    \"storage_uri\"?,    # State 2: PRIVATE omicos-drive blob of the COMPRESSED figure\n    \"original_uri\"?,   # State 2: PRIVATE omicos-drive blob of the ORIGINAL (full-res)\n    \"compressed\"?,     # is the inline (storage_uri) copy a compressed one\n    \"mime\"?, \"bytes\"?,\n}, ...]}.\n\nA row is accepted when it has `canonical_path` AND (`cdn_url` OR\n`storage_uri`). Backward compatible: a legacy payload carrying only\n`cdn_url` still works. Only the session owner may publish (same write gate\nas /append).","operationId":"publish_figure_urls_api_trajectories__session_id__figures_post","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","title":"Payload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Publish Figure Urls Api Trajectories  Session Id  Figures Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/trajectories/{session_id}/figures/upload":{"post":{"tags":["trajectories"],"summary":"Upload Figure Blob","description":"Upload a figure straight into the PRIVATE omicos-drive bucket (State 2).\n\nmultipart form:\n  - file:           the COMPRESSED figure (served inline via presign)\n  - original:       optional full-res original (for tap-to-zoom / download)\n  - canonical_path: the local figure path core keeps embedding (provenance)\n  - mime, compressed: metadata for the figure_url_map row\n\nThe blobs are content-addressed by sha256 and recorded in `user_blob_refs`\n(the same table the GC janitor + storage quota key off), and a\n`figure_url_map` row is upserted so serve-time rewrite can mint a presigned\nGET. Owner-only (same write gate as /append). 503 when the private drive is\nunconfigured (State 2 inert), 413 when the user is over quota.","operationId":"upload_figure_blob_api_trajectories__session_id__figures_upload_post","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_figure_blob_api_trajectories__session_id__figures_upload_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Upload Figure Blob Api Trajectories  Session Id  Figures Upload Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/trajectories/{session_id}/figures/raw":{"get":{"tags":["trajectories"],"summary":"Get Figure Raw","description":"Full-res tap target: 302-redirect to a fresh short-TTL presigned GET for\nthe ORIGINAL figure blob (falls back to the compressed one when there's no\nseparate original). This is what mobile tap-to-zoom + download hit.\nOwner-or-viewer gate (same visibility rules as reading the trajectory).","operationId":"get_figure_raw_api_trajectories__session_id__figures_raw_get","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"path","in":"query","required":true,"schema":{"type":"string","description":"the canonical_path of the figure","title":"Path"},"description":"the canonical_path of the figure"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/trajectories/{session_id}/meta":{"get":{"tags":["trajectories"],"summary":"Get Meta","operationId":"get_meta_api_trajectories__session_id__meta_get","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get Meta Api Trajectories  Session Id  Meta Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/trajectories/{session_id}/append":{"put":{"tags":["trajectories"],"summary":"Append Records","description":"Fast-forward append. See module docstring for the handshake.","operationId":"append_records_api_trajectories__session_id__append_put","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Append Records Api Trajectories  Session Id  Append Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/trajectories/{session_id}/full":{"put":{"tags":["trajectories"],"summary":"Replace Full","description":"Cover-write. Used by clients that lost their sync_state and want to\nreset the canonical copy (e.g. after a disk-full / partial write). Same\nquota gates as append; no FF check.","operationId":"replace_full_api_trajectories__session_id__full_put","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Replace Full Api Trajectories  Session Id  Full Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/trajectories/reconcile":{"post":{"tags":["trajectories"],"summary":"Reconcile Trajectories","description":"Mark server-side trajectories as archived when the local\nclient no longer has them. Trajectories are append-only history\n— the file on disk + summary + DB row are NEVER deleted, just\nflagged with `archived_at`. The default list endpoint hides\narchived; pass ?include_archived=1 to surface them again.\n\ncloud_sync calls this once per sweep with the full list of\nsession_ids it sees on disk; anything the server holds for the\ncalling user that isn't in the list (and isn't already archived)\ngets archived. Re-syncing a session that was previously archived\nre-activates it (clears archived_at) — useful when a user\nrestores a trajectory locally.\n\nBody: {\"session_ids\": [\"...\", \"...\"]}.\nScope = the caller's owned trajectories only.","operationId":"reconcile_trajectories_api_trajectories_reconcile_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","title":"Payload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Reconcile Trajectories Api Trajectories Reconcile Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/trajectories/{session_id}/visibility":{"patch":{"tags":["trajectories"],"summary":"Set Visibility","operationId":"set_visibility_api_trajectories__session_id__visibility_patch","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VisibilityIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Set Visibility Api Trajectories  Session Id  Visibility Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/trajectories/{session_id}/annotations":{"get":{"tags":["trajectories"],"summary":"List Annotations","operationId":"list_annotations_api_trajectories__session_id__annotations_get","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response List Annotations Api Trajectories  Session Id  Annotations Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["trajectories"],"summary":"Add Annotation","description":"Both the owner and any mentor in the session's lab may annotate\n(mentor needs the session to be visibility='lab' to access it at all,\nwhich is the default once they join).","operationId":"add_annotation_api_trajectories__session_id__annotations_post","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnnotationIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Add Annotation Api Trajectories  Session Id  Annotations Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/trajectories/{session_id}/annotations/{annotation_id}/resolve":{"post":{"tags":["trajectories"],"summary":"Resolve Annotation","description":"Resolve flag is stored by appending a status record (rather than\nrewriting the file) — keeps the on-disk JSONL append-only.","operationId":"resolve_annotation_api_trajectories__session_id__annotations__annotation_id__resolve_post","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"annotation_id","in":"path","required":true,"schema":{"type":"string","title":"Annotation Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Resolve Annotation Api Trajectories  Session Id  Annotations  Annotation Id  Resolve Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/trajectories/{session_id}/critique":{"post":{"tags":["trajectories"],"summary":"Critique","description":"LLM-side critique. Synchronous stub for now — enqueue a job and\nreturn a placeholder; phase-4 replaces this with a real deepseek call.\n\nThe shape returned matches what the production critique will return:\na list of `findings`, each with a `record_uuid` anchor, `severity`,\nand `body`. UI can render these as auto-annotations the mentor can\naccept / convert into real annotations.","operationId":"critique_api_trajectories__session_id__critique_post","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Critique Api Trajectories  Session Id  Critique Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/trajectories/{session_id}/fork":{"post":{"tags":["trajectories"],"summary":"Fork","description":"Create a new trajectory session seeded with the parent's records up\nto `head_uuid`. Anyone who can view the parent can fork from it; the\nnew session is owned by the caller and starts at visibility='private'\n(or 'lab' if the caller has an active lab).","operationId":"fork_api_trajectories__session_id__fork_post","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForkIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Fork Api Trajectories  Session Id  Fork Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/trajectories/{session_id}/summary":{"post":{"tags":["trajectories"],"summary":"Write Summary","description":"Append a `Summary` record condensing everything up to\n`parent_uuid`. Mirrors the local omicos-core endpoint.","operationId":"write_summary_api_trajectories__session_id__summary_post","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SummaryRequestIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Write Summary Api Trajectories  Session Id  Summary Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["trajectories"],"summary":"Get Summary","description":"Cached LLM summary for the trajectory. Same visibility filter as\nGET /api/trajectories/{id}. Returns:\n\n  - 200 with the summary JSON when cache exists\n  - 200 with `{enabled: false, ...}` when DEEPSEEK_API_KEY is unset\n  - 200 with `{enabled: true, status: \"pending\"}` when the worker hasn't\n    produced anything yet (UI shows ✨ analyzing... spinner)","operationId":"get_summary_api_trajectories__session_id__summary_get","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get Summary Api Trajectories  Session Id  Summary Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/trajectories/{session_id}/summary/refresh":{"post":{"tags":["trajectories"],"summary":"Refresh Summary","description":"Owner-initiated full re-summarize. Drops the cache and re-runs\nfrom scratch.\n\nAsync: returns {status: \"pending\"} immediately and runs the LLM\ncall in a background task. The earlier synchronous version\n(`await run_summarize` inline) tripped iOS WKWebView's\nCFNetwork-level connection-drop (\"The network connection was\nlost\", NSURLErrorNetworkConnectionLost / -1005) because LLM calls\nroutinely take 30-60s and the WebView aborts idle requests well\nbefore that. Polling /summary picks up the eventual result.","operationId":"refresh_summary_api_trajectories__session_id__summary_refresh_post","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Refresh Summary Api Trajectories  Session Id  Summary Refresh Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/trajectories/{session_id}/discuss":{"post":{"tags":["trajectories"],"summary":"Discuss Trajectory","operationId":"discuss_trajectory_api_trajectories__session_id__discuss_post","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","title":"Payload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Discuss Trajectory Api Trajectories  Session Id  Discuss Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/trajectories/{session_id}/discussions":{"get":{"tags":["trajectories"],"summary":"List Trajectory Discussions","description":"Owner-only view: every Q&A turn other viewers have had with the\nLLM about this trajectory, grouped by viewer. Used by the\n\"讨论记录\" surface in trajectory detail. Owner is excluded from\ntheir own turns (writes are skipped in /discuss for owner anyway,\nso this is a defensive filter).","operationId":"list_trajectory_discussions_api_trajectories__session_id__discussions_get","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response List Trajectory Discussions Api Trajectories  Session Id  Discussions Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/sync/users":{"get":{"tags":["admin"],"summary":"Sync Users Dump","description":"Full-table user dump for cross-region replication.\n\nThe downstream CN deployment polls this every 60s and upserts each\nrow into its local users table (US-wins). Pagination intentionally\nomitted — at the current 130-row scale the entire payload fits in\na single ~65 kB JSON response, and any future delta path\n(since=<iso>) would still need a fallback \"full reconcile\" mode\nwhich is exactly what this endpoint is. When the table approaches\n10⁴ rows, add a `?since=` filter against an `updated_at` column.","operationId":"sync_users_dump_api_admin_sync_users_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Sync Users Dump Api Admin Sync Users Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/users":{"get":{"tags":["admin"],"summary":"List Users","operationId":"list_users_api_admin_users_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string","description":"email / name / phone substring","default":"","title":"Q"},"description":"email / name / phone substring"},{"name":"plan_code","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Plan Code"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response List Users Api Admin Users Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/users/{user_id}":{"get":{"tags":["admin"],"summary":"Get User","operationId":"get_user_api_admin_users__user_id__get","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get User Api Admin Users  User Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["admin"],"summary":"Patch User","operationId":"patch_user_api_admin_users__user_id__patch","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PatchUserIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Patch User Api Admin Users  User Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/labs":{"get":{"tags":["admin"],"summary":"List Labs","operationId":"list_labs_api_admin_labs_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string","description":"lab name substring","default":"","title":"Q"},"description":"lab name substring"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response List Labs Api Admin Labs Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/labs/{lab_id}":{"get":{"tags":["admin"],"summary":"Get Lab Admin","operationId":"get_lab_admin_api_admin_labs__lab_id__get","parameters":[{"name":"lab_id","in":"path","required":true,"schema":{"type":"string","title":"Lab Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get Lab Admin Api Admin Labs  Lab Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["admin"],"summary":"Patch Lab","operationId":"patch_lab_api_admin_labs__lab_id__patch","parameters":[{"name":"lab_id","in":"path","required":true,"schema":{"type":"string","title":"Lab Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PatchLabIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Patch Lab Api Admin Labs  Lab Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/trajectories":{"get":{"tags":["admin"],"summary":"List Trajectories Admin","operationId":"list_trajectories_admin_api_admin_trajectories_get","parameters":[{"name":"owner","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Owner"}},{"name":"lab_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Lab Id"}},{"name":"q","in":"query","required":false,"schema":{"type":"string","default":"","title":"Q"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response List Trajectories Admin Api Admin Trajectories Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/storage_summary":{"get":{"tags":["admin"],"summary":"Storage Summary","description":"Per-user and per-lab byte usage. Drives the admin dashboard.","operationId":"storage_summary_api_admin_storage_summary_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Storage Summary Api Admin Storage Summary Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/stats":{"get":{"tags":["admin"],"summary":"Ops Stats","description":"Server-ops dashboard metrics for app.omicos.cn/admin: live online users,\nshort LLM-activity windows (5m/1h), DAU/WAU/MAU, registration totals +\nnew-today, and a per-plan active breakdown. All read-only aggregates.","operationId":"ops_stats_api_admin_stats_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Ops Stats Api Admin Stats Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/segmentation":{"get":{"tags":["admin"],"summary":"Segmentation","description":"User + paying-user distribution across institution tier / type / name.\n\nAnswers \"who actually uses this, and who pays\" — segmented by the derived\n`users.institution_*` columns (app/institutions.py) and scoped to one\nresearch domain, since a humanities Pro and an omics Pro are different\nbusinesses.\n\n`paid` counts users with an active PAID subscription in `domain`. Free\ngrants (promo/trial/admin) are reported separately as `granted` and are\nnever folded into `paid` — the 2026-07 humanities promo would otherwise\nmake every tier look like it converts at 97%.\n\n`unresolved` lists the most common institution strings the reference table\ndoesn't recognise yet — the work queue for growing `institutions.json`.","operationId":"segmentation_api_admin_segmentation_get","parameters":[{"name":"domain","in":"query","required":false,"schema":{"type":"string","default":"omics","title":"Domain"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":40,"title":"Limit"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Segmentation Api Admin Segmentation Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/audit_logs":{"get":{"tags":["admin"],"summary":"List Audit Logs","operationId":"list_audit_logs_api_admin_audit_logs_get","parameters":[{"name":"action_prefix","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Action Prefix"}},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":200,"title":"Limit"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response List Audit Logs Api Admin Audit Logs Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/resellers":{"get":{"tags":["admin"],"summary":"Admin List Resellers","operationId":"admin_list_resellers_api_admin_resellers_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Admin List Resellers Api Admin Resellers Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["admin"],"summary":"Admin Create Reseller","operationId":"admin_create_reseller_api_admin_resellers_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateResellerIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Admin Create Reseller Api Admin Resellers Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/resellers/{reseller_id}":{"get":{"tags":["admin"],"summary":"Admin Get Reseller","operationId":"admin_get_reseller_api_admin_resellers__reseller_id__get","parameters":[{"name":"reseller_id","in":"path","required":true,"schema":{"type":"string","title":"Reseller Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Admin Get Reseller Api Admin Resellers  Reseller Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["admin"],"summary":"Admin Patch Reseller","operationId":"admin_patch_reseller_api_admin_resellers__reseller_id__patch","parameters":[{"name":"reseller_id","in":"path","required":true,"schema":{"type":"string","title":"Reseller Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PatchResellerIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Admin Patch Reseller Api Admin Resellers  Reseller Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/resellers/{reseller_id}/allotment":{"post":{"tags":["admin"],"summary":"Admin Topup Allotment","operationId":"admin_topup_allotment_api_admin_resellers__reseller_id__allotment_post","parameters":[{"name":"reseller_id","in":"path","required":true,"schema":{"type":"string","title":"Reseller Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AllotmentIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Admin Topup Allotment Api Admin Resellers  Reseller Id  Allotment Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/resellers/{reseller_id}/admins":{"post":{"tags":["admin"],"summary":"Admin Create Reseller Admin","operationId":"admin_create_reseller_admin_api_admin_resellers__reseller_id__admins_post","parameters":[{"name":"reseller_id","in":"path","required":true,"schema":{"type":"string","title":"Reseller Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResellerAdminIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Admin Create Reseller Admin Api Admin Resellers  Reseller Id  Admins Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/resellers/{reseller_id}/api-keys":{"get":{"tags":["admin"],"summary":"Admin List Reseller Api Keys","operationId":"admin_list_reseller_api_keys_api_admin_resellers__reseller_id__api_keys_get","parameters":[{"name":"reseller_id","in":"path","required":true,"schema":{"type":"string","title":"Reseller Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Admin List Reseller Api Keys Api Admin Resellers  Reseller Id  Api Keys Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["admin"],"summary":"Admin Create Reseller Api Key","operationId":"admin_create_reseller_api_key_api_admin_resellers__reseller_id__api_keys_post","parameters":[{"name":"reseller_id","in":"path","required":true,"schema":{"type":"string","title":"Reseller Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResellerApiKeyIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Admin Create Reseller Api Key Api Admin Resellers  Reseller Id  Api Keys Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/resellers/{reseller_id}/api-keys/{key_id}":{"delete":{"tags":["admin"],"summary":"Admin Revoke Reseller Api Key","operationId":"admin_revoke_reseller_api_key_api_admin_resellers__reseller_id__api_keys__key_id__delete","parameters":[{"name":"reseller_id","in":"path","required":true,"schema":{"type":"string","title":"Reseller Id"}},{"name":"key_id","in":"path","required":true,"schema":{"type":"string","title":"Key Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Admin Revoke Reseller Api Key Api Admin Resellers  Reseller Id  Api Keys  Key Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/resellers/{reseller_id}/grants":{"get":{"tags":["admin"],"summary":"Admin Reseller Grants","operationId":"admin_reseller_grants_api_admin_resellers__reseller_id__grants_get","parameters":[{"name":"reseller_id","in":"path","required":true,"schema":{"type":"string","title":"Reseller Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":200,"title":"Limit"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Admin Reseller Grants Api Admin Resellers  Reseller Id  Grants Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/invoicing/invoices":{"get":{"tags":["admin"],"summary":"Admin List Invoice Requests","description":"跨用户的开票队列。用户自助提交的请求不会通知任何人,按 user_id 查\n的那个端点也就找不到它们——这里按状态列出「谁在等」。","operationId":"admin_list_invoice_requests_api_admin_invoicing_invoices_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"pending | issued | voided","title":"Status"},"description":"pending | issued | voided"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Admin List Invoice Requests Api Admin Invoicing Invoices Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/invoicing/users/{user_id}":{"get":{"tags":["admin"],"summary":"Admin Invoicing Summary","description":"一个用户的可开票状况:已付/已开/剩余(分),逐订单明细,及其发票列表。","operationId":"admin_invoicing_summary_api_admin_invoicing_users__user_id__get","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Admin Invoicing Summary Api Admin Invoicing Users  User Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/invoicing/users/{user_id}/invoices":{"post":{"tags":["admin"],"summary":"Admin Record Invoice","description":"按选定的已付订单开一张票(金额=这些订单之和)。校验归属/已付/未重复开。","operationId":"admin_record_invoice_api_admin_invoicing_users__user_id__invoices_post","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecordInvoiceIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Admin Record Invoice Api Admin Invoicing Users  User Id  Invoices Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/invoicing/invoices/{invoice_id}/issue":{"post":{"tags":["admin"],"summary":"Admin Issue Invoice","description":"回填税务系统真实票号,状态置「已开」。","operationId":"admin_issue_invoice_api_admin_invoicing_invoices__invoice_id__issue_post","parameters":[{"name":"invoice_id","in":"path","required":true,"schema":{"type":"string","title":"Invoice Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IssueInvoiceIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Admin Issue Invoice Api Admin Invoicing Invoices  Invoice Id  Issue Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/invoicing/invoices/{invoice_id}/void":{"post":{"tags":["admin"],"summary":"Admin Void Invoice","description":"作废一张票,其订单释放回「可开票」。","operationId":"admin_void_invoice_api_admin_invoicing_invoices__invoice_id__void_post","parameters":[{"name":"invoice_id","in":"path","required":true,"schema":{"type":"string","title":"Invoice Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoidInvoiceIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Admin Void Invoice Api Admin Invoicing Invoices  Invoice Id  Void Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/reseller/auth/login":{"post":{"tags":["reseller"],"summary":"Reseller Login","operationId":"reseller_login_api_reseller_auth_login_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/reseller/customers":{"get":{"tags":["reseller"],"summary":"List Customers","operationId":"list_customers_api_reseller_customers_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Q"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/reseller/customers/{user_id}":{"get":{"tags":["reseller"],"summary":"Get Customer","operationId":"get_customer_api_reseller_customers__user_id__get","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/reseller/allotment":{"get":{"tags":["reseller"],"summary":"Get Allotment","operationId":"get_allotment_api_reseller_allotment_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/reseller/customers/{user_id}/grant-seat":{"post":{"tags":["reseller"],"summary":"Grant Seat","operationId":"grant_seat_api_reseller_customers__user_id__grant_seat_post","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GrantSeatIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/reseller/customers/provision":{"post":{"tags":["reseller"],"summary":"Provision Customer","description":"Find/create a phone account, link it, and grant the purchased seat.\n\nExact retries by either idempotency key or partner order return the stored\nresponse. A reused key/order with a changed payload is rejected. Partner\nidentity claims deliberately do not stamp ``phone_verified_at``: the first\nOmicOS login still proves phone ownership through the normal SMS OTP flow.","operationId":"provision_customer_api_reseller_customers_provision_post","parameters":[{"name":"X-Omicos-Partner-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Omicos-Partner-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerProvisionIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Provision Customer Api Reseller Customers Provision Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/reseller/customers/{user_id}/revoke":{"post":{"tags":["reseller"],"summary":"Revoke","operationId":"revoke_api_reseller_customers__user_id__revoke_post","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RevokeIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/markers/tissues":{"get":{"tags":["markers"],"summary":"List Tissues","operationId":"list_tissues_api_markers_tissues_get","parameters":[{"name":"species","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Species"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response List Tissues Api Markers Tissues Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/markers/celltypes":{"get":{"tags":["markers"],"summary":"List Celltypes","operationId":"list_celltypes_api_markers_celltypes_get","parameters":[{"name":"tissue","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tissue"}},{"name":"species","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Species"}},{"name":"disease","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disease"}},{"name":"polarity","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Polarity"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":200,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response List Celltypes Api Markers Celltypes Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/markers/diseases":{"get":{"tags":["markers"],"summary":"List Diseases","operationId":"list_diseases_api_markers_diseases_get","parameters":[{"name":"tissue","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tissue"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response List Diseases Api Markers Diseases Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/markers/gene_celltype_counts":{"get":{"tags":["markers"],"summary":"List Gene Celltype Counts","operationId":"list_gene_celltype_counts_api_markers_gene_celltype_counts_get","parameters":[{"name":"species","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Species"}},{"name":"tissue","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tissue"}},{"name":"disease","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disease"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response List Gene Celltype Counts Api Markers Gene Celltype Counts Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/markers/celltype_markers":{"get":{"tags":["markers"],"summary":"Get Celltype Markers","operationId":"get_celltype_markers_api_markers_celltype_markers_get","parameters":[{"name":"cell_names","in":"query","required":true,"schema":{"type":"string","description":"Pipe-delimited list of canonical cell_name_standardized values, e.g. `CD8-positive, alpha-beta cytotoxic T cell|natural killer cell`. Get the canonical strings from /api/markers/celltypes.","title":"Cell Names"},"description":"Pipe-delimited list of canonical cell_name_standardized values, e.g. `CD8-positive, alpha-beta cytotoxic T cell|natural killer cell`. Get the canonical strings from /api/markers/celltypes."},{"name":"species","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Species"}},{"name":"tissue","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tissue"}},{"name":"disease","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disease"}},{"name":"include_negative","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Include Negative"}},{"name":"pmids_per_gene","in":"query","required":false,"schema":{"type":"integer","maximum":50,"minimum":0,"default":5,"title":"Pmids Per Gene"}},{"name":"args","in":"query","required":true,"schema":{"title":"Args"}},{"name":"kwargs","in":"query","required":true,"schema":{"title":"Kwargs"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CelltypeMarkersResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/markers/score":{"post":{"tags":["markers"],"summary":"Score","operationId":"score_api_markers_score_post","parameters":[{"name":"args","in":"query","required":true,"schema":{"title":"Args"}},{"name":"kwargs","in":"query","required":true,"schema":{"title":"Kwargs"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScoreClusterIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScoreResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/user_settings/image_host":{"get":{"tags":["image_host"],"summary":"Get Image Host Config","description":"Returns the current config. Token MASKED by default to keep it\nout of UI rendering paths (DOM dumps, screenshots, etc).\n\nPass `?reveal=1` to get the unmasked token — the bearer auth gates\nthis to the owning user, and the response is over HTTPS, so it's\nno less secure than any other auth-protected resource. Used by\nomicos-core's figure_publisher and BenchView's manual upload path\nso they can talk to the image host (e.g. GitHub Contents API)\nDIRECTLY without ever routing image bytes through this server.\n\nAlso surfaces which `active_provider` values are eligible for\nthis user — UIs gate the toggle on this list (e.g. 'cloud' only\nfor paid plans; 'github' only when github config has repo+token).","operationId":"get_image_host_config_api_user_settings_image_host_get","parameters":[{"name":"reveal","in":"query","required":false,"schema":{"type":"integer","maximum":1,"minimum":0,"default":0,"title":"Reveal"}},{"name":"authorization","in":"query","required":false,"schema":{"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get Image Host Config Api User Settings Image Host Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["image_host"],"summary":"Put Image Host Config","description":"Save config. If incoming `token` is empty/masked we keep the\nexisting real token — lets the SPA edit other fields without\nre-prompting for the PAT.\n\n`active_provider` is validated against the user's eligibility:\n'cloud' requires a paid plan; 'github' requires the github config\nto have repo+token after the merge step. Invalid choices → 400.","operationId":"put_image_host_config_api_user_settings_image_host_put","parameters":[{"name":"authorization","in":"query","required":false,"schema":{"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageHostConfigIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Put Image Host Config Api User Settings Image Host Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["image_host"],"summary":"Delete Image Host Config","operationId":"delete_image_host_config_api_user_settings_image_host_delete","parameters":[{"name":"authorization","in":"query","required":false,"schema":{"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Delete Image Host Config Api User Settings Image Host Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/user_settings/image_host/active":{"get":{"tags":["image_host"],"summary":"Get Active Provider","description":"Tiny, cheap endpoint that omicos-core polls to decide whether\nto add the markdown-image directive to the system prompt. No\nconfig or token in the response — just the routing intent.\n\nWhen there's NO saved row (truly unset), a paid user on the cloud web\nclient (`X-Client-Platform: web`) defaults to 'cloud' so figures offload\nwithout a manual toggle; every other unset case stays 'off' (fail closed).\nAn EXPLICIT row is always honored verbatim (never override github/off).","operationId":"get_active_provider_api_user_settings_image_host_active_get","parameters":[{"name":"authorization","in":"query","required":false,"schema":{"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get Active Provider Api User Settings Image Host Active Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/user_settings/image_host/test":{"post":{"tags":["image_host"],"summary":"Test Image Host Upload","description":"Performs a real upload using the saved config. Used by the Keys\npage's \"Test upload\" button. inline=true uploads a 1×1 transparent\nPNG so the user can confirm the round-trip without picking a file.","operationId":"test_image_host_upload_api_user_settings_image_host_test_post","parameters":[{"name":"authorization","in":"query","required":false,"schema":{"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageHostTestIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Test Image Host Upload Api User Settings Image Host Test Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/user_settings/image_host/upload":{"post":{"tags":["image_host"],"summary":"Upload Image For Chat","description":"Upload an image and return a public URL the chat can embed as\n`![](url)`. Routing follows the user's `active_provider` toggle:\n\n  - 'off'    → 400 (\"image hosting disabled\"); caller should\n               not have invoked this in the first place.\n  - 'github' → upload via the saved github config.\n  - 'cloud'  → /api/files blob with is_public=1.\n\nCalled by omicos-core's figure_publisher and (in future) by\nBenchView for manual uploads.","operationId":"upload_image_for_chat_api_user_settings_image_host_upload_post","parameters":[{"name":"authorization","in":"query","required":false,"schema":{"title":"Authorization"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_image_for_chat_api_user_settings_image_host_upload_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Upload Image For Chat Api User Settings Image Host Upload Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/health":{"get":{"summary":"Health","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object","title":"Response Health Health Get"}}}}}}},"/api/auth/register":{"post":{"summary":"Register","operationId":"register_api_auth_register_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Register Api Auth Register Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/login":{"post":{"summary":"Login","operationId":"login_api_auth_login_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Login Api Auth Login Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/sms/available":{"get":{"summary":"Sms Available","description":"Runtime feature-flag for the phone-OTP login surface.\n\nThe SPA queries this once at login-page mount; when it returns\n`{available: false}` the LoginForm hides the Email/Phone tab\ntoggle (and the entire phone path) even if its consumer passed\n`enable-phone-login`. This lets us ship the binary with the\ncode in place before Aliyun's carrier 报备 (5-7 working days)\ncompletes — the feature flips on when `OMICOS_ALIYUN_SMS_*`\nenv vars get set on the box, with no redeploy.\n\nAlso useful for staging / dev where SMS isn't wired up but the\nrest of the app still needs to render.\n\nNo auth required — this endpoint reveals zero PII, just the\nserver-side configuration state.","operationId":"sms_available_api_auth_sms_available_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Response Sms Available Api Auth Sms Available Get"}}}}}}},"/api/auth/sms/send":{"post":{"summary":"Sms Send","description":"Mint + send a 6-digit OTP to the caller's phone.\n\nRate limiting:\n  - Per phone: 1 OTP / 60s. Repeat sends within the window reuse\n    the existing code (we DON'T burn Aliyun quota on retries) and\n    return the same {\"sent\": true} response so the client can't\n    probe \"is this number registered\" by timing.\n  - Per IP: tracked via the phone_otp_codes.ip column; same 60s.\n    Stops a single attacker from sweeping codes across N phones.\n\nReturns\n-------\n200 {\"sent\": true} on success (also when the per-phone cooldown\n    short-circuited — see above).\n400 if phone fails E.164 +86 validation.\n503 if Aliyun SMS isn't configured server-side.\n502 if Aliyun returned a non-OK Code (network / template / quota).","operationId":"sms_send_api_auth_sms_send_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SmsSendIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Sms Send Api Auth Sms Send Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/sms/login":{"post":{"summary":"Sms Login","description":"Verify the OTP and return {token, user}.\n\nFirst successful verify for a phone number that has never been\nseen creates a new user row (phone-only signup). Subsequent\nverifies for that same number return the existing user.","operationId":"sms_login_api_auth_sms_login_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SmsLoginIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Sms Login Api Auth Sms Login Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/email/available":{"get":{"summary":"Email Available","description":"Whether email-OTP login can be served right now.\n\nTrue iff Resend/SMTP is configured AND OTP_TTL_SECONDS > 0\n(latter is constant; included for symmetry with sms_available()).\nThe SPA's LoginForm hides the \"use a code instead\" link when this\nreturns False so the password row stays the only path.","operationId":"email_available_api_auth_email_available_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Response Email Available Api Auth Email Available Get"}}}}}}},"/api/auth/email/send":{"post":{"summary":"Email Send","description":"Mint + send a 6-digit OTP to `payload.email`.\n\nAnti-enumeration: the response is identical whether or not the\naddress belongs to a registered, activated user. We still skip the\nResend call (and the per-account quota burn) for missing /\nunactivated accounts — there's no point delivering codes nobody\nasked for — but the caller can't tell from the response.\n\nRate limits mirror /api/auth/sms/send:\n  - per-email cooldown: 1 OTP / 60s. Within-window requests reuse\n    the existing code (no fresh send) but return {sent:true}.\n  - per-IP cooldown across emails: 1 send / 60s / IP, to stop a\n    single attacker from sweeping the table.","operationId":"email_send_api_auth_email_send_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailSendIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Email Send Api Auth Email Send Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/email/login":{"post":{"summary":"Email Login","description":"Verify the OTP and return {token, user}.\n\nExisting-account only — unknown emails get the generic\n\"no pending code\" error so the endpoint can't be enumerated.\nInactivated accounts are also rejected here (the activation flow\nis still the only way to flip is_active=1).","operationId":"email_login_api_auth_email_login_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailLoginIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Email Login Api Auth Email Login Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/plan_token":{"post":{"summary":"Plan Token Endpoint","description":"Issue a signed plan claim. The caller is already authenticated\nvia the session bearer token; we read their `plan_code` from the\ndatabase (NOT from the request body — that's the whole point of\nsigning it). 24h TTL by default; configurable via\nOMICOS_PLAN_TOKEN_TTL_SECONDS for staging.\n\nAuth resolution is inlined here (rather than `Depends(current_user)`)\nso this endpoint can attach a machine-readable `code` to its 401/403\nbodies. omicos-core's plan_token renewer maps any 401/403 from this\nroute to RenewReason::SessionExpired purely on status code (see the\ncross-repo contract); the `code:\"session_expired\"` field is an\nadditive diagnostic so SPAs/clients can match without status-only\ninference. The 503 signing/key-failure path below stays as-is and\ncore maps it to RenewReason::ServerSigningError. The HTTP status\ncodes and `detail` strings are UNCHANGED from the shared\n`current_user` dependency, so behavior for status-only consumers is\nidentical.","operationId":"plan_token_endpoint_api_auth_plan_token_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Plan Token Endpoint Api Auth Plan Token Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/.well-known/jwks.json":{"get":{"summary":"Jwks Endpoint","description":"Public key set for verifying plan tokens. **No authentication\nrequired** — this is the whole point of asymmetric signing. The\nomicos-admin and omicos-core boot processes hit this once at\nstartup (and re-fetch on cache expiry, 24h default) to pick up\nkey rotation without redeployment.","operationId":"jwks_endpoint__well_known_jwks_json_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Jwks Endpoint  Well Known Jwks Json Get"}}}}}}},"/api/auth/activate/{token}":{"get":{"summary":"Activate Account","description":"User clicked the link in their activation email.\n\nReturns a small standalone HTML page (success or error) — this\nendpoint is reached by a top-level browser navigation, not an\nXHR from the SPA, so JSON wouldn't help anyone. On success we\nalso 302 to the SPA's home with `?activated=1` so the SPA can\nsurface a one-time toast.","operationId":"activate_account_api_auth_activate__token__get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/resend-activation":{"post":{"summary":"Resend Activation","description":"Re-send the activation email if the recipient still has an\ninactive account. Anti-enumeration: we ALWAYS return 200 with the\nsame payload regardless of whether the email is in our DB or\nalready activated. Otherwise an attacker can probe which emails\nare registered by watching the response shape.","operationId":"resend_activation_api_auth_resend_activation_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResendActivationIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Resend Activation Api Auth Resend Activation Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/verify-otp":{"post":{"summary":"Verify Otp","description":"Verify the 6-digit code the user typed into the SPA's inline\nOTP input. On success, flip `is_active=1`, clear the OTP columns,\nand return a session token + user payload — same shape as\n`/api/auth/login` so the SPA can drop straight into the\nauthenticated experience without a second round-trip.\n\nFailure modes, distinguished by `detail.code` so the SPA can\nshow targeted error copy:\n  - `invalid_code`           — wrong digits, attempts now bumped\n  - `code_expired`           — TTL elapsed\n  - `too_many_attempts`      — attempt counter ≥ OTP_MAX_ATTEMPTS,\n    the code is invalidated; user must request a new one\n  - `no_pending_activation`  — no outstanding code (already\n    active, or user never registered)\n\nAnti-enumeration: when the email doesn't exist we return the\nSAME `invalid_code` shape as a wrong-digits attempt so an\nattacker can't probe which addresses are registered by watching\nfor differently-shaped errors.","operationId":"verify_otp_api_auth_verify_otp_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyOtpIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Verify Otp Api Auth Verify Otp Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/wechat-oauth":{"post":{"summary":"Wechat Oauth","operationId":"wechat_oauth_api_auth_wechat_oauth_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WechatOAuthIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Wechat Oauth Api Auth Wechat Oauth Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/wechat-oauth/qr":{"post":{"summary":"Wechat Oauth Qr","description":"Mint a WeChat web-QR for the SPA to render as a plain <img>.\n\nFetches the qrconnect page server-side (Referer = our auth host so\nWeChat is happy) and returns the freshly-minted `uuid`; the SPA then\nshows `qr_url` directly. No iframe, so WeChat's anti-embedding JS\nnever runs.","operationId":"wechat_oauth_qr_api_auth_wechat_oauth_qr_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WechatQrIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Wechat Oauth Qr Api Auth Wechat Oauth Qr Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/wechat-oauth/qr-poll":{"get":{"summary":"Wechat Oauth Qr Poll","description":"Long-poll WeChat for the scan/confirm of the QR minted by /qr.\n\nReturns one of:\n  {\"ok\": True, token, user}        — confirmed (also stashed for /poll)\n  {\"ok\": False, \"pending\": True, \"scanned\": bool}\n  {\"ok\": False, \"error\": \"expired\" | \"cancelled\" | \"...\"}\nThe SPA re-calls on `pending` (each call already blocks ~25s upstream,\nso this is not a busy loop).","operationId":"wechat_oauth_qr_poll_api_auth_wechat_oauth_qr_poll_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"type":"string","default":"","title":"State"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Wechat Oauth Qr Poll Api Auth Wechat Oauth Qr Poll Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/wechat-oauth/poll":{"get":{"summary":"Wechat Oauth Poll","description":"Opener-page fallback for WeChat web-QR login. The popup's\nwindow.opener is severed by open.weixin.qq.com's COOP header, so the\ncallback can't postMessage the result back. Instead the callback's\n/api/auth/wechat-oauth response is stashed by `state`; the opener\npolls here until it appears. Consumed on first hit. No auth — `state`\nis the high-entropy CSRF token only the opener + its own popup know.","operationId":"wechat_oauth_poll_api_auth_wechat_oauth_poll_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"type":"string","default":"","title":"State"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Wechat Oauth Poll Api Auth Wechat Oauth Poll Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/wechat-login":{"post":{"summary":"Wechat Login","operationId":"wechat_login_api_auth_wechat_login_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WechatLoginIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Wechat Login Api Auth Wechat Login Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/me":{"get":{"summary":"Me","operationId":"me_api_auth_me_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Me Api Auth Me Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/password":{"post":{"summary":"Set Password","description":"Set or change the account's login password.\n\n- Accounts WITHOUT a usable password (phone-OTP-only → password_hash NULL,\n  or WeChat-only → \"wechat-only\" sentinel) set one for the first time and\n  do NOT need `current_password`. This is what lets a phone-OTP user\n  enable email/phone + password login from Settings.\n- Accounts that already have a password must supply the correct\n  `current_password` to change it.","operationId":"set_password_api_auth_password_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetPasswordIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Set Password Api Auth Password Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/accept-terms":{"post":{"summary":"Accept Terms","description":"Record the user's agreement to the Terms of Service + Privacy Policy.\n\nThe redesigned login UI enforces a consent checkbox client-side (the\nform can't be submitted unchecked) and remembers it per-device so a\nreturning user sees it pre-checked. This endpoint persists the legal\nrecord server-side. Stamped ONCE — first acceptance wins, so a later\nre-login doesn't keep moving the timestamp. Idempotent + best-effort:\nthe client calls it post-auth and ignores failures.","operationId":"accept_terms_api_auth_accept_terms_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Accept Terms Api Auth Accept Terms Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/password/reset":{"post":{"summary":"Reset Password","description":"Forgot-password / OTP reset. Prove ownership of the registered phone or\nemail with the OTP it received (reuses the SMS/email OTP that already backs\nlogin), then set a new password — NO auth token and NO old password needed.\nReturns {token, user} so the SPA can drop straight into a logged-in session.\nThe same endpoint backs the Settings \"用验证码改密\" path.","operationId":"reset_password_api_auth_password_reset_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetPasswordIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Reset Password Api Auth Password Reset Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/account/delete":{"post":{"summary":"Request Account Deletion","description":"Self-service account deletion (cooling-off).\n\nRe-authenticates the caller, then flags the row with a deletion\ntimestamp. The account is immediately treated as logged-out (see\ncurrent_user), but NO data is removed yet — a successful re-login\nwithin ACCOUNT_DELETION_GRACE_DAYS clears the flag (undo). A daily\nsweep (_purge_due_deletions) hard-deletes rows past the window; the\nPostgres FK CASCADE then removes all owned data, and the sweep also\nclears OTP codes and decrements blob ref-counts.\n\nRe-auth path is decided by the account type:\n- has a real password  → must supply the correct `password`.\n- phone/email-OTP-only → must supply a fresh `code` (requested via\n  the existing /api/auth/sms|email/send), verified against the same\n  OTP table login uses.","operationId":"request_account_deletion_api_auth_account_delete_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteAccountIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Request Account Deletion Api Auth Account Delete Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/regional_endpoint":{"get":{"summary":"Auth Regional Endpoint","description":"Pre-login region probe for omicos-core (the Rust sidecar on the\nuser's machine).\n\nReturns the **default** cloud_base (auth.omicos.cn — the 备案\nregion, where most signups land) plus the full list of supported\nauth hosts. omicos-core hits this once at startup if it has no\npersisted config; after the user actually logs in, the auth\nresponse carries the user's home cloud_base (per their\n`users.region`) and the sidecar switches to that for everything\ndownstream.\n\nNo auth required — the response is identical across users and\nleaks no PII; it's just service discovery.\n\nResponse shape (CN-only after the region consolidation):\n    { \"default_cloud_base\": \"https://auth.omicos.cn\",\n      \"supported\":          [\"https://auth.omicos.cn\"] }","operationId":"auth_regional_endpoint_api_auth_regional_endpoint_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Auth Regional Endpoint Api Auth Regional Endpoint Get"}}}}}}},"/api/auth/logout":{"post":{"summary":"Logout","operationId":"logout_api_auth_logout_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Response Logout Api Auth Logout Post"}}}}}}},"/api/auth/profile":{"patch":{"summary":"Update Profile","description":"Update the caller's researcher profile.\n\nThis used to write ONLY `name` while accepting the whole ProfileIn — so\nomicOS-ui's ProfileModal, which PATCHes the full form, silently lost\ninstitution / research_area / usage_purpose on every save. Worse, it then\nreported success and merged the form into its local copy, so the edit\nlooked applied until the next login reloaded the untouched row from the\nserver. The fields have been in the payload model since the profile form\nshipped; only the UPDATE was missing.\n\nSemantics: a field is written only when the client SENDS it non-empty.\nProfileIn defaults every field to \"\", and PATCH is a partial update, so a\ncaller that omits a field (omicos-server-ui sends name only) must not have\nit blanked. Clearing a field is therefore not expressible here — no client\noffers it, and silently wiping data on a partial payload is the worse\nfailure. `title` additionally goes through the closed vocabulary.","operationId":"update_profile_api_auth_profile_patch","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProfileIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Update Profile Api Auth Profile Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/user/profile/bind-phone-send":{"post":{"summary":"Bind Phone Send","description":"Mint + send an OTP to the target phone for the caller to verify.\n\nPre-flight rejects if the phone is already bound to a DIFFERENT\nuser (409). Re-binding the SAME phone the caller already owns is\nallowed — useful as a \"re-verify\" affordance.\n\nShares the phone_otp_codes table + 60s per-phone cooldown with\n/api/auth/sms/send; the OTP minted here can also be consumed by\nthat endpoint and vice versa. The /confirm endpoint below is the\nonly thing that enforces who-owns-what at verify time, so cross-\nflow OTP use is benign.","operationId":"bind_phone_send_api_user_profile_bind_phone_send_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BindPhoneSendIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Bind Phone Send Api User Profile Bind Phone Send Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/user/profile/bind-phone-confirm":{"post":{"summary":"Bind Phone Confirm","description":"Verify OTP + write phone to the caller's user row.\n\nMirrors /api/auth/sms/login's OTP verification (expiry, max\nattempts, constant-time compare) but updates the existing row\ninstead of creating one. The phone_verified_at column is bumped\nto the current UTC stamp so cross-region sync's newest-wins\ntreats this as the authoritative state.\n\nReturns the updated public-user shape — same as /api/auth/me —\nso the SPA can refresh its Pinia store without a separate fetch.","operationId":"bind_phone_confirm_api_user_profile_bind_phone_confirm_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BindPhoneConfirmIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Bind Phone Confirm Api User Profile Bind Phone Confirm Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/user/profile/bind-email-send":{"post":{"summary":"Bind Email Send","description":"Mint + DELIVER an OTP to the target email for the caller to verify.\n\nPre-flight 409s if the email already belongs to a DIFFERENT account\n(the partial unique index would catch it at write time, but a clear\n409 up front is friendlier). Re-verifying the SAME email the caller\nalready owns passes through. Per-email 60s cooldown reuses the pending\ncode (idempotent {sent:true}).","operationId":"bind_email_send_api_user_profile_bind_email_send_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BindEmailSendIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Bind Email Send Api User Profile Bind Email Send Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/user/profile/bind-email-confirm":{"post":{"summary":"Bind Email Confirm","description":"Verify the OTP + write the email to the caller's user row.\n\nMirrors /api/auth/email/login's verification (expiry, max attempts,\nconstant-time compare) but updates the existing row instead of\ncreating one. Returns the updated public-user shape (same as\n/api/auth/me) so the SPA can refresh its store without a refetch.","operationId":"bind_email_confirm_api_user_profile_bind_email_confirm_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BindEmailConfirmIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Bind Email Confirm Api User Profile Bind Email Confirm Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/user/profile/unbind-phone":{"post":{"summary":"Unbind Phone","description":"Detach the phone from the caller's account.\n\nRefuses when the caller has NEITHER email+password NOR WeChat\nbound — phone is their only login factor and unbinding would\nlock them out. The SPA must already gate the \"unbind\" button on\nthe same condition so this is a server-side defense in depth.","operationId":"unbind_phone_api_user_profile_unbind_phone_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Unbind Phone Api User Profile Unbind Phone Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/plans":{"get":{"summary":"Plans","description":"Plan catalog — single source of truth for prices, taglines,\nand entitlements. The SPA (BenchSubscription.vue) fetches this on\nmount so admin price changes don't require a frontend redeploy.\n\nOptional `?domain=` (per-domain entitlements, EPIC omicos-admin#287):\nthe catalog is currently identical across domains (pricing parity), so\nthe same plan list is returned and the response is simply TAGGED with\nthe resolved `domain` so the SPA knows which domain it's rendering a\ncheckout for. Unknown domains fall back to 'omics' rather than 4xx —\nthis is a read-only catalog, and a bogus domain shouldn't break the\npricing page. Omitting the param is byte-compatible with the old\nresponse apart from the additive `domain` field.","operationId":"plans_api_plans_get","parameters":[{"name":"domain","in":"query","required":false,"schema":{"type":"string","default":"omics","title":"Domain"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Plans Api Plans Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/subscription":{"get":{"summary":"Subscription","operationId":"subscription_api_subscription_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Subscription Api Subscription Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/subscription/change":{"post":{"summary":"Change Subscription","operationId":"change_subscription_api_subscription_change_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanChangeIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Change Subscription Api Subscription Change Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/billing/checkout":{"post":{"summary":"Billing Checkout","operationId":"billing_checkout_api_billing_checkout_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Billing Checkout Api Billing Checkout Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/billing/lab/pricing":{"get":{"summary":"Lab Pricing","description":"Seat prices the LAB purchase UI renders. Returns the price the CALLER\nwould pay (academic vs commercial, resolved from their affiliation) plus\nthe always-visible commercial rate, so the SPA can show '学术价 ¥79 (原价\n¥199)'. Sourced from PLAN_DEFINITIONS so it tracks any admin re-pricing.","operationId":"lab_pricing_api_billing_lab_pricing_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Lab Pricing Api Billing Lab Pricing Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/billing/lab/{lab_id}/seats":{"post":{"summary":"Change Lab Seats","description":"Grow or shrink a PAID lab's seat count. Owner-only.\n\n- **Grow** (seats > current cap): a prorated top-up order for the added\n  seats over the CURRENT subscription's remaining days. The added seats\n  co-terminate with that sub (no expiry extension) — the paid webhook\n  lifts `seat_quota` to the new total. Returns a `pay_url`.\n- **Shrink** (seats < current cap, ≥ active members ≥ min): takes effect\n  immediately; surplus is NOT refunded (不退不换). Returns the lab.","operationId":"change_lab_seats_api_billing_lab__lab_id__seats_post","parameters":[{"name":"lab_id","in":"path","required":true,"schema":{"type":"string","title":"Lab Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SeatChangeIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Change Lab Seats Api Billing Lab  Lab Id  Seats Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/billing/orders/{order_no}":{"get":{"summary":"Billing Order Status","operationId":"billing_order_status_api_billing_orders__order_no__get","parameters":[{"name":"order_no","in":"path","required":true,"schema":{"type":"string","title":"Order No"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Billing Order Status Api Billing Orders  Order No  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/luria/credits/checkout":{"post":{"summary":"Luria Credits Checkout","description":"Buy a Luria credits pack. Lives in main (not luria.py) so it can reuse\nthe billing rails verbatim: payment_orders row + provider pay URL + the\nsame webhooks. plan_code='luria_credits' makes _finalize_order_paid credit\nthe ledger instead of activating a subscription; the pack's credit count\nrides in discount_meta so finalize never has to re-look-up prices that\nmay have changed between checkout and payment.","operationId":"luria_credits_checkout_api_luria_credits_checkout_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LuriaCreditsCheckoutIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Luria Credits Checkout Api Luria Credits Checkout Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/billing/invoicing":{"get":{"summary":"Billing Invoicing Summary","description":"可开票余额 + 逐单明细 + 本人发票记录。\n\n余额基数只含微信/支付宝的真实收款:Apple 内购由 Apple 开票,promo /\nreseller 免费发放和已退款订单都不进基数(见 app/invoicing.py)。","operationId":"billing_invoicing_summary_api_billing_invoicing_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Billing Invoicing Summary Api Billing Invoicing Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/billing/invoicing/invoices":{"post":{"summary":"Billing Request Invoice","description":"提交开票申请。金额 = 所选订单之和,不接受任意金额——发票按订单绑定,\n这正是「同一单开不出第二张票、余额永不为负」的来源。\n\n落库即 `pending`,并立刻占住这些订单(可开票余额随之减少),否则同一批\n订单可以被连着提交多次。管理员回填真实票号后转 `issued`;作废才会把\n订单释放回可开票。","operationId":"billing_request_invoice_api_billing_invoicing_invoices_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SelfInvoiceIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Billing Request Invoice Api Billing Invoicing Invoices Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/billing/invoicing/invoices/{invoice_id}/withdraw":{"post":{"summary":"Billing Withdraw Invoice","description":"撤回自己还没开具的申请,所占订单立即释放回可开票余额。\n\n只能撤「待开票」且属于本人的那一张:已开具的是真实票据,红冲要走运营。\n⚠️ 撤回不退还当日提交次数 —— 上限统计的是提交,不是存活的申请。","operationId":"billing_withdraw_invoice_api_billing_invoicing_invoices__invoice_id__withdraw_post","parameters":[{"name":"invoice_id","in":"path","required":true,"schema":{"type":"string","title":"Invoice Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Billing Withdraw Invoice Api Billing Invoicing Invoices  Invoice Id  Withdraw Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/referral":{"get":{"summary":"Get Referral","description":"Referral dashboard: the caller's code + invite/conversion stats + credit\nbalance (drives the settings-page referral card).","operationId":"get_referral_api_referral_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get Referral Api Referral Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/referral/claim":{"post":{"summary":"Claim Referral Endpoint","description":"Attribute the (new) caller to a referral code and grant the referee\nbenefits — a 3-day Plus taste + the one-time first-month-¥9 Plus offer.\nCalled by the SPA right after signup when a `?ref=` code is present.\nIdempotent + best-effort; returns `{ok, reason?}` (never raises).","operationId":"claim_referral_endpoint_api_referral_claim_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReferralClaimIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Claim Referral Endpoint Api Referral Claim Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/billing/webhook/mock":{"post":{"summary":"Billing Webhook Mock","description":"Dev-only: manual mock-provider webhook to finalise an order.\n\nNot enabled in prod (Phase 2 ships real WeChat / Alipay webhooks\nwith signature verification). For dev / e2e tests, POST\n`{\"order_no\": \"ord_...\"}` to bump the order to 'paid' and trigger\n`activate_subscription`. Idempotent on the order_no — a duplicate\ncallback is a no-op rather than a double-charge.","operationId":"billing_webhook_mock_api_billing_webhook_mock_post","requestBody":{"content":{"application/json":{"schema":{"type":"object","title":"Payload"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Billing Webhook Mock Api Billing Webhook Mock Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/billing/webhook/wechat":{"post":{"summary":"Billing Webhook Wechat","description":"WeChat Pay v3 async notification endpoint.\n\nVerifies the RSA signature with the platform certificate (lib\nhandles auto-refresh), decrypts the AES-GCM payload using\napiv3_key, and on `trade_state=SUCCESS` marks the order paid +\nactivates the user's subscription.\n\nIdempotent: WeChat may retry the same notification multiple\ntimes across the 50-minute retry window — a re-delivery for an\nalready-paid order is a no-op and still returns SUCCESS so the\nretries stop.\n\nReturns the WeChat-required ack body (`{code, message}`) with\nHTTP 200 on success and HTTP 200 + FAIL ack on bad payload (so\nwe don't accidentally bury misconfigurations under WeChat retries).","operationId":"billing_webhook_wechat_api_billing_webhook_wechat_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/billing/webhook/alipay":{"post":{"summary":"Billing Webhook Alipay","description":"Alipay 当面付 async-notify endpoint.\n\nVerifies RSA2 signature over the urlencoded form using the\nAlipay public key, then on `trade_status in {TRADE_SUCCESS,\nTRADE_FINISHED}` finalizes the order via the shared\n`_finalize_order_paid()` helper.\n\nIdempotent on re-delivery (Alipay retries up to 25 times across\n~24h with backoff). The handler must return the literal text\nbody `success` (HTTP 200) for Alipay to stop retrying — any\nother body, status, or even a JSON `{\"status\":\"success\"}`\ntriggers indefinite retries until the 25-attempt window expires.","operationId":"billing_webhook_alipay_api_billing_webhook_alipay_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/billing/apple/verify":{"post":{"summary":"Billing Apple Verify","description":"Verify an Apple proof and claim/apply its transaction exactly once.\n\nStoreKit 2 JWS is preferred; StoreKit 1 ``receiptData`` is the explicit\nfallback. Client product, duration, and transaction identifiers are never\nauthoritative. Apple ``expiresDate`` is applied as an absolute boundary.","operationId":"billing_apple_verify_api_billing_apple_verify_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppleVerifyIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Billing Apple Verify Api Billing Apple Verify Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/billing/apple/repair":{"post":{"summary":"Billing Apple Repair","description":"Reconcile the caller's Apple entitlement from Apple's authoritative\nserver state — the pull complement to /verify's push.\n\nUse when a client push never landed (a StoreKit receipt race, a lost\nrequest) so the local entitlement is missing while Apple still shows an\nactive subscription. Given any transaction id in the user's chain, we pull\nthe subscription's current transactions from the App Store Server API and\n(re)apply the ACTIVE one.\n\nUnlike /verify's proof-possession path, repair only grants a transaction\nwhose Apple-signed ``appAccountToken`` is present (positive attribution);\napply_verified_transaction then enforces it resolves to this user, so\nknowing a stranger's transaction id can never claim their subscription.\nIdempotent: apply uses an absolute expiry, so repeated repairs never add a\nperiod.","operationId":"billing_apple_repair_api_billing_apple_repair_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppleRepairIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Billing Apple Repair Api Billing Apple Repair Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/billing/webhook/apple":{"post":{"summary":"Billing Webhook Apple","description":"App Store Server Notifications V2 endpoint.\n\nVerifies the signed payload, decodes the nested transaction, and\nstores ``notificationUUID`` and verified transaction metadata, then either\nprocesses it once or leaves it pending until the chain is claimed by an\nauthenticated verification. Invalid signatures are acknowledged without\nmutation; verified notifications that cannot be persisted/applied return\n5xx so Apple retries.","operationId":"billing_webhook_apple_api_billing_webhook_apple_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/billing/subscription":{"get":{"summary":"Billing Subscription","description":"Detailed view of the current subscription row (expires_at,\nauto_renew, period) FOR `domain`. Distinct from `/api/subscription`\nwhich returns the plan + entitlements; this one is for the\n'Manage subscription' UI. `domain` defaults to 'omics' so existing\ncallers are unchanged; pass `?domain=humanities_social` for the\nother membership.","operationId":"billing_subscription_api_billing_subscription_get","parameters":[{"name":"domain","in":"query","required":false,"schema":{"type":"string","default":"omics","title":"Domain"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Billing Subscription Api Billing Subscription Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/billing/subscriptions":{"get":{"summary":"Billing Subscriptions","description":"Every currently-active subscription, ONE per research domain\n(omics, humanities_social, ...), each with its own expires_at /\nauto_renew / cancelled_at.\n\nThis is the per-domain source of truth for the client's on-app-open\nexpiry reminder: the flat `domain_plans` token claim carries only the\ntier, and `/api/auth/plan_token`'s expires_at is the 24h token TTL —\nneither reflects the actual membership expiry.","operationId":"billing_subscriptions_api_billing_subscriptions_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Billing Subscriptions Api Billing Subscriptions Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/billing/expiry-reminders":{"get":{"summary":"Billing Expiry Reminders","description":"Per-domain memberships that are expired or expiring soon — the data\nsource for the on-app-open renewal reminder popup. Each item names its\n`domain` and `plan_code`, so the client prompts renewal for the right\nmembership (omics vs humanities_social). An empty list = nothing to nag.","operationId":"billing_expiry_reminders_api_billing_expiry_reminders_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Billing Expiry Reminders Api Billing Expiry Reminders Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/billing/subscription/cancel":{"post":{"summary":"Billing Subscription Cancel","description":"Cancel renewal — claude/codex-style \"stop my subscription\".\n\nNo money moves: the current period continues working until\n`expires_at`, at which point the hourly `expire_due_subscriptions`\ncron demotes the user to community as it would have anyway. The\nbehavior change is purely UI:\n  - `cancelled_at` gets a timestamp so the SPA can render the\n    cancelled state (\"已取消，<date> 到期后变为 Free\") instead of\n    the open-ended \"active\".\n  - `auto_renew` is flipped FALSE for future-proofing (a hosted\n    WeChat Pay 协议支付 / Alipay 周期扣款 product would gate on\n    this).\n\nIdempotent — cancelling a sub that is already cancelled is a\nno-op and returns the same payload.","operationId":"billing_subscription_cancel_api_billing_subscription_cancel_post","parameters":[{"name":"domain","in":"query","required":false,"schema":{"type":"string","default":"omics","title":"Domain"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Billing Subscription Cancel Api Billing Subscription Cancel Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/billing/subscription/uncancel":{"post":{"summary":"Billing Subscription Uncancel","description":"Reverse the `cancel` endpoint — flip the active sub back to\n\"will-renew\" status. Symmetric counterpart to `cancel`; the user\nhas nothing to pay (the current sub is still valid until\nexpires_at). Useful for users who change their mind.","operationId":"billing_subscription_uncancel_api_billing_subscription_uncancel_post","parameters":[{"name":"domain","in":"query","required":false,"schema":{"type":"string","default":"omics","title":"Domain"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Billing Subscription Uncancel Api Billing Subscription Uncancel Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/register":{"post":{"summary":"V1 Register","operationId":"v1_register_api_v1_auth_register_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response V1 Register Api V1 Auth Register Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/login":{"post":{"summary":"V1 Login","operationId":"v1_login_api_v1_auth_login_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response V1 Login Api V1 Auth Login Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/me":{"get":{"summary":"V1 Me","operationId":"v1_me_api_v1_auth_me_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response V1 Me Api V1 Auth Me Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/logout":{"post":{"summary":"V1 Logout","operationId":"v1_logout_api_v1_auth_logout_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Response V1 Logout Api V1 Auth Logout Post"}}}}}}},"/api/v1/auth/profile":{"patch":{"summary":"V1 Update Profile","operationId":"v1_update_profile_api_v1_auth_profile_patch","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProfileIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response V1 Update Profile Api V1 Auth Profile Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/process-token":{"post":{"summary":"Process Token","operationId":"process_token_api_process_token_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"string"},"title":"Response Process Token Api Process Token Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/cli-device-code":{"post":{"summary":"Cli Device Code","description":"Step 1 — CLI starts pairing. No auth required.","operationId":"cli_device_code_api_auth_cli_device_code_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Cli Device Code Api Auth Cli Device Code Post"}}}}}}},"/api/auth/cli-approve":{"post":{"summary":"Cli Approve","description":"Step 3 — browser (already-authenticated SPA) approves the pairing\nby submitting the human-typeable `user_code`.","operationId":"cli_approve_api_auth_cli_approve_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/_CliApproveBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"string"},"title":"Response Cli Approve Api Auth Cli Approve Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/cli-poll":{"post":{"summary":"Cli Poll","description":"Step 4 — CLI polls. Returns one of pending / approved / expired.\nOn `approved`, mints fresh user_token + process_token bound to the\nuser who confirmed the pairing in their browser.","operationId":"cli_poll_api_auth_cli_poll_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/_CliPollBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Cli Poll Api Auth Cli Poll Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes":{"get":{"summary":"List Processes","description":"Cacheable process list. Emits a content `ETag` and honours\n`If-None-Match` for 304s, saving the ~9 KB body on every unchanged\npoll (the SPA polls this every ~1.8 s).\n\nThe validator is an ETag, not `Last-Modified`: a max(updated_at)\nhigh-water mark moves backwards when a process is deleted, which\nproduced stale 304s (a phantom process lingering in the client's\ncached list). See the inline comment below.\n\nBackwards compatible: clients that send no conditional header — or\nsend the old `If-Modified-Since` (now ignored) — receive the full\nJSON body with a 200, so they always render the current set.","operationId":"list_processes_api_processes_get","parameters":[{"name":"If-None-Match","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"If-None-Match"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response List Processes Api Processes Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/launch-token":{"post":{"summary":"Process Launch Token","operationId":"process_launch_token_api_processes__process_id__launch_token_post","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Process Launch Token Api Processes  Process Id  Launch Token Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/launch/{launch_token}":{"get":{"summary":"Resolve Launch Token","operationId":"resolve_launch_token_api_launch__launch_token__get","parameters":[{"name":"launch_token","in":"path","required":true,"schema":{"type":"string","title":"Launch Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Resolve Launch Token Api Launch  Launch Token  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}":{"patch":{"summary":"Rename Process","operationId":"rename_process_api_processes__process_id__patch","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenameIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Rename Process Api Processes  Process Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/commands":{"post":{"summary":"Process Command","operationId":"process_command_api_processes__process_id__commands_post","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommandIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Process Command Api Processes  Process Id  Commands Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/projects":{"get":{"summary":"List Chat Projects","operationId":"list_chat_projects_api_projects_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response List Chat Projects Api Projects Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"summary":"Create Chat Project","operationId":"create_chat_project_api_projects_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatProjectInput"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Create Chat Project Api Projects Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/projects/{project_id}":{"put":{"summary":"Rename Chat Project","operationId":"rename_chat_project_api_projects__project_id__put","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string","title":"Project Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatProjectRenameInput"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Rename Chat Project Api Projects  Project Id  Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Delete Chat Project","operationId":"delete_chat_project_api_projects__project_id__delete","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string","title":"Project Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/ChatProjectDeleteInput"},{"type":"null"}],"title":"Payload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Delete Chat Project Api Projects  Project Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/projects/assignments/{session_id}":{"put":{"summary":"Assign Conversation Project","operationId":"assign_conversation_project_api_projects_assignments__session_id__put","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatProjectAssignmentInput"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Assign Conversation Project Api Projects Assignments  Session Id  Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/projects/import":{"post":{"summary":"Import Chat Projects","description":"Idempotent legacy-localStorage import. Project ids are preserved, so\nretrying the same import is a no-op and the returned id_map is stable.","operationId":"import_chat_projects_api_projects_import_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatProjectImportInput"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Import Chat Projects Api Projects Import Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/process_conversations":{"get":{"summary":"List User Process Conversations","description":"Per-user conversation INDEX across every process the user has ever\nregistered — served entirely from the cloud mirror, no process RPC.\n\nThe per-process route below answers \"what does THIS process hold\" and\nneeds the caller to already know the process id. A pure-cloud client\n(Luria) needs the inverse: \"everything of mine, wherever it ran\",\nincluding processes that are offline or whose sandbox was destroyed —\nthose still have live rows here because a terminated container never\nderegisters, and history-survives-the-sandbox is the product promise.\nReading the mirror only (never RPC) keeps this one cheap DB query\nregardless of how many processes the user has accumulated.\n\nRows are the same summaries `_cached_conversations` serves, plus the\nowning process's identity so the client can build a runtime binding\nand route detail reads to `/api/processes/{pid}/conversations/{sid}`\n(which falls back to this same mirror when the process is offline).\n\nDeliberately NOT the per-process route's two-phase read: this is the\nonly mirror query with no process_id bound, and a long-lived account\nholds mirror rows by the ten-thousands with GIGABYTES of history\nblobs (first dogfood account: 10,842 rows / 1.7 GB). Backfilling\n`last_active` from those bodies took >60 s and got the response\ntruncated mid-flight. A LISTING must never read bodies: rows whose\nsummary predates `last_active_local` sort by `synced_at` and render\nwithout a timestamp until their next sync or detail read. Same\nreasoning behind the LIMIT — the sidebar shows the recent tail, not\nan unbounded account history in one response.","operationId":"list_user_process_conversations_api_process_conversations_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":200,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response List User Process Conversations Api Process Conversations Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/conversations":{"get":{"summary":"List Process Conversations","description":"Return only the conversation summaries (title / session_id /\nlast_active). Per-conversation full body is fetched lazily on\ndemand by `get_process_conversation` when the user actually opens\none — eager-syncing every detail here used to cost N × RPC for\nevery page load and was the main source of perceived \"slowness\"\non the history panel.","operationId":"list_process_conversations_api_processes__process_id__conversations_get","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response List Process Conversations Api Processes  Process Id  Conversations Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"summary":"Create Process Conversation","operationId":"create_process_conversation_api_processes__process_id__conversations_post","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Create Process Conversation Api Processes  Process Id  Conversations Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/conversation_sync/state":{"get":{"summary":"Get Process Conversation Sync State","operationId":"get_process_conversation_sync_state_api_processes__process_id__conversation_sync_state_get","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get Process Conversation Sync State Api Processes  Process Id  Conversation Sync State Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/llm/providers/status":{"get":{"summary":"Get Process Llm Provider Status","operationId":"get_process_llm_provider_status_api_processes__process_id__llm_providers_status_get","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get Process Llm Provider Status Api Processes  Process Id  Llm Providers Status Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/kernel/list":{"get":{"summary":"Get Process Kernel List","description":"Relay a remote process's available Python interpreters / conda envs so a\nuser can pick one when switching that process's env (like the local kernel\npicker), instead of typing the interpreter path. Owner-gated; empty when the\nprocess is offline.","operationId":"get_process_kernel_list_api_processes__process_id__kernel_list_get","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get Process Kernel List Api Processes  Process Id  Kernel List Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/conversations/sync":{"post":{"summary":"Sync Process Conversation","operationId":"sync_process_conversation_api_processes__process_id__conversations_sync_post","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Sync Process Conversation Api Processes  Process Id  Conversations Sync Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/conversations/{session_id}/append":{"post":{"summary":"Append Process Conversation","description":"Incremental counterpart to `.../conversations/sync` (phase 3).\n\nThe full-sync endpoint re-uploads the WHOLE history every sweep — during a\nlong turn the owning core re-sends a growing 1-2 MB body every 10s, and the\nserver re-parses it. This endpoint takes only the messages appended since the\ncore's last successful push:\n\n    { \"base_seq\": N, \"messages\": [...], \"meta\": {...}, \"goal\": ..., ... }\n\n`base_seq` is the core's high-water mark — the `last_seq` it believes the\nserver holds. The contract, keyed on the server's ACTUAL `last_seq`:\n\n  * base_seq == last_seq → append all `messages` (the happy path).\n  * base_seq <  last_seq → the leading `last_seq - base_seq` messages were\n    already applied (a retry or a core that resumed from a stale cursor);\n    drop them and append only the genuine tail. Fully-duplicate → no-op.\n    This is what makes the push idempotent under retries.\n  * base_seq >  last_seq → the core is AHEAD of us: we are missing messages\n    it never delivered, so an append would splice a gap. Refuse with 409\n    `need_full`; the core falls back to a full `/sync`.\n\nHistory stays sourced from `history_json` during rollout, so this rebuilds\nthe full array (old + fresh) and hands it to the same\n`cache_and_broadcast_conversation` the full sync uses — identical dual-write,\n`conv_delta` broadcast, figure rewrite and goal handling. The saving is on\nthe WIRE (and the server's JSON parse), not yet the blob write (phase 4).","operationId":"append_process_conversation_api_processes__process_id__conversations__session_id__append_post","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Append Process Conversation Api Processes  Process Id  Conversations  Session Id  Append Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/conversations/{session_id}":{"delete":{"summary":"Delete Process Conversation","description":"Delete a conversation that lives on ANOTHER omicos process — an SSH\nremote's own store, or a cloud process (sherlock / racknerd).\n\nThe SPA can only DELETE against its own local core (`/api/conversations/`\nalways resolves there), so a conversation owned by a different process had\nno delete path at all: the row came back on the next list. This is that\npath.\n\nThree steps, in this order for a reason:\n\n  1. Check the process belongs to the caller. Explicitly, rather than\n     leaning on `_process_rpc`'s own check, because step 3 is allowed to\n     fail and must not be what gates access.\n  2. Stamp the cloud ARCHIVE rows deleted (never remove them — see\n     `_mark_conversation_deleted`). Recording the intent BEFORE acting on\n     it is what makes the delete durable: every read path filters on the\n     stamp, so the conversation leaves the user's view whether or not the\n     owning process is reachable, and the tombstone guarantees the files\n     go even if step 3 never lands. Relaying first would mean a failure\n     here could destroy the user's files without recording that we did.\n  3. Relay to the owning process so it drops the real on-disk copy now\n     rather than on its next sweep. Best-effort ON PURPOSE — offline\n     (409), timed out (504), or a core that errored are all just \"not yet\"\n     once the tombstone exists, so none of them may fail the delete.\n     Delete means delete on the user's own machines; only the archive\n     survives.","operationId":"delete_process_conversation_api_processes__process_id__conversations__session_id__delete","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"delete_project_memory","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Delete Project Memory"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Delete Process Conversation Api Processes  Process Id  Conversations  Session Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"summary":"Get Process Conversation","description":"Cache-first: if we already have a cached body for this\nconversation, serve it without an RPC. Writes (chat/stream\ncompletion + create_process_conversation) refresh the cache, so\nthe cache stays current on the happy path. Clients can force a\nfresh fetch with `?refresh=1` (e.g. after a device-side import\nor when the user explicitly hits a sync button). Offline\nprocesses always serve from cache via the existing fallback.\n\n`?since_seq=N` returns only the messages appended after index N\nin the cached history. Combined with the response's `last_seq`,\nclients can do efficient cursor-based catchup (e.g. SPA reconnecting\nits WebSocket sends `since_seq=<last_value_we_saw>` to fetch only\nmessages it missed instead of replaying the entire 1-2 MB body).\nAppend-only contract is guaranteed by the sidecar's trajectory.jsonl\nwriter.\n\n`?tail=N` returns only the last N messages (the default conversation\nopen — the SPA no longer pulls the whole body just to show the bottom\nof the thread). `?before_seq=C&limit=M` pages backward: the M messages\nimmediately before index C (scroll-up). Both add `earliest_seq` (index\nof the first returned message) and `has_more` (older messages exist\nabove) to the response so the client knows whether to keep paging.\nThese are mutually exclusive with each other and take precedence over\n`since_seq`; sending none of them keeps the legacy full-body shape.","operationId":"get_process_conversation_api_processes__process_id__conversations__session_id__get","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"refresh","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Refresh"}},{"name":"since_seq","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Since Seq"}},{"name":"tail","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Tail"}},{"name":"before_seq","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Before Seq"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"figures_raw","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Figures Raw"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get Process Conversation Api Processes  Process Id  Conversations  Session Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/conversations/deletions":{"post":{"summary":"Report Process Conversation Deletions","description":"A process reporting conversations the user deleted on ITS OWN disk, so\nthe cloud hides the archived copies (`cloud_sync_conversations`'s deletion\npush).\n\nThe cloud cannot work this out for itself: conversation sync is push-only,\nso a deleted conversation merely stops being pushed — indistinguishable\nfrom an idle one. Its archive row therefore stayed `deleted_at=''` forever\nand resurfaced on the user's other devices every time this process went\noffline and the list fell back to the cache.\n\nDistinct from `DELETE .../conversations/{session_id}` on purpose. That\nroute is for a conversation living on some OTHER process and relays the\ndelete to its owner. Here the owner is the caller and the files are\nalready gone, so there is nothing to relay — and relaying would bounce the\ndelete straight back into the caller's own store, which re-logs it and\nre-reports it on every sweep.\n\nIdempotent: re-reporting an id already stamped marks nothing (the UPDATE\nis guarded on `deleted_at=''`), and an id the cloud never saw marks\nnothing either. Both are fine — the caller clears its log on 200 either\nway.","operationId":"report_process_conversation_deletions_api_processes__process_id__conversations_deletions_post","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationDeletionsIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Report Process Conversation Deletions Api Processes  Process Id  Conversations Deletions Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/conversations/tombstones":{"get":{"summary":"List Conversation Tombstones","description":"Conversations this user deleted, for the owning core's sync sweep to\nreap from its local store (`cloud_sync_conversations::sweep_once`).\n\nDeliberately scoped to the USER, not to `process_id` — a conversation\nexists in several stores at once (the local shell + the owning process's\nreal copy), and \"the user deleted S\" is true for every machine holding S.\n`process_id` is here for auth and to keep the route next to its siblings.\n\n`since` is the caller's cursor (the `cursor` from its last pull): only\ntombstones sequenced after it are returned, so a daemon that has been off\nfor a month gets exactly what it missed and a busy one re-pulls nothing.\nReaping is idempotent, so a cursor lost to a crash costs one redundant\n(no-op) delete, never a missed one.\n\nGrouped by session_id because one conversation has a row per process that\never synced it, each with its own seq — the puller wants one tombstone per\nconversation, carrying the newest seq so the cursor clears them all.","operationId":"list_conversation_tombstones_api_processes__process_id__conversations_tombstones_get","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"since","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Since"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response List Conversation Tombstones Api Processes  Process Id  Conversations Tombstones Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/conversations/{session_id}/model":{"put":{"summary":"Set Process Conversation Model","description":"Persist the per-conversation model choice so it survives reload and\nsyncs across devices. Upserts ONLY the provider/model columns on the\nconversation cache row — every other column (history/meta/route/…) is\nuntouched, so a concurrent core→cache trajectory sync won't clobber the\nmodel and this PUT won't clobber history. If the row doesn't exist yet\n(model picked before the first trajectory sync created it), a minimal\nrow is inserted; the later sync fills in the rest via its own upsert,\nwhich deliberately omits these two columns.","operationId":"set_process_conversation_model_api_processes__process_id__conversations__session_id__model_put","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationModelIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Set Process Conversation Model Api Processes  Process Id  Conversations  Session Id  Model Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/plan/{session_id}":{"get":{"summary":"Get Process Plan","operationId":"get_process_plan_api_processes__process_id__plan__session_id__get","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get Process Plan Api Processes  Process Id  Plan  Session Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"summary":"Write Process Plan","description":"Replace the plan body. Body: `{plan: str}`.","operationId":"write_process_plan_api_processes__process_id__plan__session_id__post","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Write Process Plan Api Processes  Process Id  Plan  Session Id  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/plan/{session_id}/enter":{"post":{"summary":"Enter Process Plan","description":"Put the session into plan mode. Body: `{notes?: str}`.","operationId":"enter_process_plan_api_processes__process_id__plan__session_id__enter_post","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Enter Process Plan Api Processes  Process Id  Plan  Session Id  Enter Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/plan/{session_id}/exit":{"post":{"summary":"Exit Process Plan","description":"Leave plan mode. Body: `{approved: bool}`.","operationId":"exit_process_plan_api_processes__process_id__plan__session_id__exit_post","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Exit Process Plan Api Processes  Process Id  Plan  Session Id  Exit Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/plan/{session_id}/toggle":{"post":{"summary":"Toggle Process Plan Task","description":"Flip one GFM checkbox in the plan body. Body: `{task_index: int,\nchecked?: bool}`. Core does the markdown surgery.","operationId":"toggle_process_plan_task_api_processes__process_id__plan__session_id__toggle_post","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Toggle Process Plan Task Api Processes  Process Id  Plan  Session Id  Toggle Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/plan/{session_id}/approve":{"post":{"summary":"Approve Process Plan","description":"Approve the pending plan. Body: `{note?: str}`.","operationId":"approve_process_plan_api_processes__process_id__plan__session_id__approve_post","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Approve Process Plan Api Processes  Process Id  Plan  Session Id  Approve Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/plan/{session_id}/reject":{"post":{"summary":"Reject Process Plan","description":"Reject the pending plan. Body: `{note?: str}`.","operationId":"reject_process_plan_api_processes__process_id__plan__session_id__reject_post","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Reject Process Plan Api Processes  Process Id  Plan  Session Id  Reject Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/notebook/session/{session_id}/export/stream":{"post":{"summary":"Export Process Notebook Stream","description":"Stream the notebook export SSE from the owning process. Body: `{config}`.","operationId":"export_process_notebook_stream_api_processes__process_id__notebook_session__session_id__export_stream_post","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/notebook/session/{session_id}/export":{"post":{"summary":"Export Process Notebook","description":"Non-streaming export: return the assembled .ipynb (or a 422 when the\nconversation ran no Python). Body: `{config}`.","operationId":"export_process_notebook_api_processes__process_id__notebook_session__session_id__export_post","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/threads/{session_id}/goal":{"get":{"summary":"Get Thread Goal","description":"Live-or-cached goal snapshot for one thread.\n\nShape: `{goal: <Goal>|null, process_offline: bool}`.\n\nWhen the process is online, fetch fresh from local (the local\nsidecar is the canonical store) and opportunistically update the\ncache so the next offline read is current. When it's offline,\nserve the last cached snapshot.","operationId":"get_thread_goal_api_processes__process_id__threads__session_id__goal_get","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get Thread Goal Api Processes  Process Id  Threads  Session Id  Goal Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"summary":"Create Thread Goal","description":"Create / replace the goal for a thread. Body: `{objective: str,\ntoken_budget?: int}`. Returns the persisted goal. 409 when the\ntarget process is offline (mobile UI greys the action button).","operationId":"create_thread_goal_api_processes__process_id__threads__session_id__goal_post","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","title":"Payload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Create Thread Goal Api Processes  Process Id  Threads  Session Id  Goal Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"summary":"Patch Thread Goal","description":"Mutate the goal's status. Body: `{action: 'pause'|'resume'|\n'complete'|'clear', expected_goal_id?: str}`. `clear` bypasses the\noptimistic-lock check on purpose (matches local behavior). Returns\n`{ok, goal, outcome: 'applied'|'unchanged'|'stale_id'}`.","operationId":"patch_thread_goal_api_processes__process_id__threads__session_id__goal_patch","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","title":"Payload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Patch Thread Goal Api Processes  Process Id  Threads  Session Id  Goal Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Delete Thread Goal","description":"Convenience alias for `PATCH {action: 'clear'}`. Lets mobile\nSPAs use semantic HTTP verbs (DELETE = remove the goal) instead of\na PATCH-with-action soup.","operationId":"delete_thread_goal_api_processes__process_id__threads__session_id__goal_delete","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Delete Thread Goal Api Processes  Process Id  Threads  Session Id  Goal Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/agent/chat/cancel":{"post":{"summary":"Cancel Process Chat","description":"Stop the running turn on `process_id`. Body: `{session_id}`.\n\nReturns core's `{success, cancelled, session_id}` verbatim — `cancelled`\nis the honest answer from the machine that owns the turn, which is the\nwhole point of this route existing.","operationId":"cancel_process_chat_api_processes__process_id__agent_chat_cancel_post","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Cancel Process Chat Api Processes  Process Id  Agent Chat Cancel Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/agent/chat/guidance":{"post":{"summary":"Guide Process Chat","description":"Mid-turn guidance for a turn running on `process_id`. Body carries\n`{action, session_id, ...}` — core dispatches on `action`\n(enqueue/guide/send/remove/list), so every verb rides this one route.","operationId":"guide_process_chat_api_processes__process_id__agent_chat_guidance_post","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Guide Process Chat Api Processes  Process Id  Agent Chat Guidance Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/conversations/{session_id}/followups":{"post":{"summary":"Process Conversation Followups","description":"Follow-up suggestions for a conversation on a cloud process. The SPA\nused a bare fetch here, which in cloud-only lands on a bridge that does\nnot exist — 「后续建议不可用」 forever on luria.omicos.cn. Same body\npassthrough as guidance; core soft-fails generation to\n`{suggestions: [], unavailable: true}` so an offline LLM never turns\nthis into an error banner.","operationId":"process_conversation_followups_api_processes__process_id__conversations__session_id__followups_post","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Process Conversation Followups Api Processes  Process Id  Conversations  Session Id  Followups Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/sandbox/roots":{"get":{"summary":"Process Sandbox Roots","description":"Which roots this core will browse — the pane asks first.","operationId":"process_sandbox_roots_api_processes__process_id__sandbox_roots_get","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Process Sandbox Roots Api Processes  Process Id  Sandbox Roots Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/files/list":{"get":{"summary":"Process Files List","description":"One directory of the process's workspace, relative to its root.\n\n`Cache-Control: no-store`: this GET has no explicit caching headers\notherwise, and a directory listing changes any time the campaign writes a\nnew file — a browser that opportunistically cached one response (no\nvalidator needed to do so; absence of Cache-Control is not the same as\n\"don't cache\") kept serving it back verbatim on every later visit to this\nsame conversation, across ordinary reloads, showing a workspace frozen at\nwhatever it looked like the first time this exact URL was fetched.","operationId":"process_files_list_api_processes__process_id__files_list_get","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"path","in":"query","required":false,"schema":{"type":"string","default":"","title":"Path"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Process Files List Api Processes  Process Id  Files List Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/files/list_abs":{"get":{"summary":"Process Files List Abs","description":"Absolute-path listing. Core enforces its own root containment — the\nrelay must not invent a second, weaker rule, so the value is passed\nthrough re-encoded and judged there.\n\n`Cache-Control: no-store` — same staleness risk as `list` above, and the\nsame fix.","operationId":"process_files_list_abs_api_processes__process_id__files_list_abs_get","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"abspath","in":"query","required":false,"schema":{"type":"string","default":"","title":"Abspath"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Process Files List Abs Api Processes  Process Id  Files List Abs Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/files/open":{"post":{"summary":"Process Files Open","description":"A file's content, for the workspace file viewer — the read counterpart\nto `list`/`list_abs` above. Registered ahead of the mutate catch-all below\n(FastAPI matches by registration order, and `{action}` would otherwise\nswallow this literal path first) since `open` is core's one file-content\nroute that is itself a POST (it takes the path in a JSON body, not a query\nstring) rather than because it belongs in the mutate allow-list.","operationId":"process_files_open_api_processes__process_id__files_open_post","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Process Files Open Api Processes  Process Id  Files Open Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/files/{action}":{"post":{"summary":"Process Files Mutate","description":"create / save / delete / rename / move / copy — JSON in, JSON out.\n\nAllow-listed rather than forwarded blindly: `action` lands in a core URL\npath, and an open passthrough would let any caller address routes this\nshim was never meant to expose.","operationId":"process_files_mutate_api_processes__process_id__files__action__post","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"action","in":"path","required":true,"schema":{"type":"string","title":"Action"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Process Files Mutate Api Processes  Process Id  Files  Action  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/agent/session/{session_id}/history":{"get":{"summary":"Process Agent Session History","description":"Read active-turn state from the process that owns the session.","operationId":"process_agent_session_history_api_processes__process_id__agent_session__session_id__history_get","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"runtime_only","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Runtime Only"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Process Agent Session History Api Processes  Process Id  Agent Session  Session Id  History Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/runtime/status":{"get":{"summary":"Process Runtime Status","description":"Read runtime status from the selected process, not the web host.","operationId":"process_runtime_status_api_processes__process_id__runtime_status_get","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Process Runtime Status Api Processes  Process Id  Runtime Status Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/agent/session/{session_id}/stream":{"get":{"summary":"Stream Process Agent Session","description":"Re-attach to a live turn on the process that owns the session.","operationId":"stream_process_agent_session_api_processes__process_id__agent_session__session_id__stream_get","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"since_seq","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Since Seq"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/memory/session/consolidate":{"post":{"summary":"Consolidate Process Session Memory","description":"Kick off consolidation of a session's trajectory on `process_id` — the\nmachine that HAS the trajectory. Body: `{session_id, config?}`.","operationId":"consolidate_process_session_memory_api_processes__process_id__memory_session_consolidate_post","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Consolidate Process Session Memory Api Processes  Process Id  Memory Session Consolidate Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/memory/session/status":{"get":{"summary":"Process Session Memory Status","description":"Lifecycle of that consolidation run. `session_id` is a QUERY param\nhere, matching core; it is re-encoded rather than forwarded raw so a\nsession id needing escaping can't reshape the path.","operationId":"process_session_memory_status_api_processes__process_id__memory_session_status_get","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"session_id","in":"query","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Process Session Memory Status Api Processes  Process Id  Memory Session Status Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/project-memory/dossier/organize":{"post":{"summary":"Organize Process Project Dossier","description":"Run the beginner Project Dossier organizer on the process that owns\nthe selected conversation model. Core only transforms the supplied draft;\nauthenticated Project Memory persistence remains a separate cloud call.","operationId":"organize_process_project_dossier_api_processes__process_id__project_memory_dossier_organize_post","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Organize Process Project Dossier Api Processes  Process Id  Project Memory Dossier Organize Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/campaign/{session_id}":{"get":{"summary":"Process Session Campaign","description":"Read the optimisation-campaign ledger for a cloud-process session.\n\ncore exposes it at `GET /api/campaign/{session_id}` (the ledger the organism\nworkbench renders). The SPA's `sessionFetch('/api/campaign/<sid>')` becomes\n`/api/processes/<pid>/campaign/<sid>` for a process-bound conversation, and\nthis relays it down the process's WS socket via the same `_process_rpc`\nbridge as plan/memory/goal. `session_id` is re-encoded rather than forwarded\nraw so an id needing escaping can't reshape the core path. A session with no\ncampaign is `{\"campaign\": null}` with 200 from core — not an error.","operationId":"process_session_campaign_api_processes__process_id__campaign__session_id__get","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Process Session Campaign Api Processes  Process Id  Campaign  Session Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/campaign/{session_id}/events":{"get":{"summary":"Process Session Campaign Events","description":"The campaign's raw event stream — what the organism timeline and the\nrole seats render. Same relay as the detail route above; without it a\nprocess-bound conversation's `sessionFetch('/api/campaign/<sid>/events')`\n404s and the SPA reads that as \"core predates the events route\".","operationId":"process_session_campaign_events_api_processes__process_id__campaign__session_id__events_get","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Process Session Campaign Events Api Processes  Process Id  Campaign  Session Id  Events Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/campaign/{session_id}/candidates":{"get":{"summary":"Process Session Campaign Candidates","description":"One page of the campaign ledger (the raw-evidence table). Query params\nare re-encoded rather than forwarded raw, same reasoning as session_id.","operationId":"process_session_campaign_candidates_api_processes__process_id__campaign__session_id__candidates_get","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Process Session Campaign Candidates Api Processes  Process Id  Campaign  Session Id  Candidates Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/campaign/{session_id}/propose":{"get":{"summary":"Process Session Campaign Propose","description":"Next-batch proposal, generated on demand by the owning core.","operationId":"process_session_campaign_propose_api_processes__process_id__campaign__session_id__propose_get","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"batch","in":"query","required":false,"schema":{"type":"integer","default":8,"title":"Batch"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Process Session Campaign Propose Api Processes  Process Id  Campaign  Session Id  Propose Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/agents":{"get":{"summary":"Get Process Agents","description":"Forward to local sidecar's `GET /api/agents`. Returns\n`{agents: [{id, name, description, toolsets, tier, skills,\n            category, category_order, summary, use_when,\n            example_prompts, instructions_summary}, ...]}`.","operationId":"get_process_agents_api_processes__process_id__agents_get","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get Process Agents Api Processes  Process Id  Agents Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/user/model_preference":{"get":{"summary":"Get Model Preference","operationId":"get_model_preference_api_user_model_preference_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get Model Preference Api User Model Preference Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"summary":"Put Model Preference","operationId":"put_model_preference_api_user_model_preference_put","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Put Model Preference Api User Model Preference Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/user/preferences":{"get":{"summary":"Get User Preferences","operationId":"get_user_preferences_api_user_preferences_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get User Preferences Api User Preferences Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"summary":"Put User Preferences","operationId":"put_user_preferences_api_user_preferences_put","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Put User Preferences Api User Preferences Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/user/agent_team":{"get":{"summary":"Get User Agent Team","description":"Return `{members, selected_id, updated_at}`. Empty defaults\nwhen the user has no row yet — callers fall back to local default.","operationId":"get_user_agent_team_api_user_agent_team_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get User Agent Team Api User Agent Team Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"summary":"Put User Agent Team","description":"Partial update. Body keys (all optional):\n  - `members`: list[str]   — replaces the activated set entirely\n  - `selected_id`: str     — replaces the active-agent id\nOmitted keys preserve the current row's value.","operationId":"put_user_agent_team_api_user_agent_team_put","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Put User Agent Team Api User Agent Team Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/user/custom_providers":{"get":{"summary":"Get User Custom Providers","description":"Return `{providers, updated_at}`. Empty defaults when no row yet.","operationId":"get_user_custom_providers_api_user_custom_providers_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get User Custom Providers Api User Custom Providers Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"summary":"Put User Custom Providers","description":"Whole-list replace of the user's custom provider definitions\n(last-writer-wins). Accepts either a bare list or `{providers: [...]}`.\nRejects any payload carrying a secret-looking field.","operationId":"put_user_custom_providers_api_user_custom_providers_put","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Put User Custom Providers Api User Custom Providers Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/memory":{"get":{"summary":"List Memory","description":"SPA list. Default: ACTIVE ONLY (invalidated_on IS NULL), ranked by\n(importance DESC, COALESCE(last_accessed_at, updated_at) DESC,\nupdated_at DESC). `?include_superseded=1` includes invalidated rows.\nDegrades on DB failure (last-good / 503) instead of 500-storming.","operationId":"list_memory_api_memory_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response List Memory Api Memory Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/memory/manifest":{"get":{"summary":"Memory Manifest","description":"Per-slug sync manifest + a rolled-up `version`. omicos-core diffs\nthis against its local manifest.json and only re-pulls slugs whose\nmeta_version changed (a metadata-only flip re-pulls even when the\nbody hash is unchanged).\n\nMUST include ALL non-deleted rows — active AND invalidated — so core\nnever garbage-collects a superseded note's local .md (supersession is\nexpire-don't-delete; only an explicit DELETE removes a slug).\n\nDegrades on DB failure (last-good / 503) instead of 500-storming the\nmost-polled endpoint (the 2026-06-24 cascade).","operationId":"memory_manifest_api_memory_manifest_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Memory Manifest Api Memory Manifest Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/memory/session_search":{"post":{"summary":"Memory Session Search","description":"On-demand recall over the CALLER'S OWN past sessions (trajectories) —\nthe recall half of two-tier memory (Hermes-style): curated memory is\nalways in context; this searches \"what happened in past sessions\".\n\n**Plan-gated (Plus/Pro).** community has a 0 monthly quota and is\nshort-circuited to a self-describing `gated` payload BEFORE any disk scan,\nso the free tier can never trigger the per-call summary+raw file scan. Paid\ntiers are bounded three ways: the SAME visibility window as trajectories\n(you can only recall what you can still see), per-plan scan caps\n(max_sessions + raw-fallback budget), and a monthly call quota.\n\nSummary-first with a bounded raw-JSONL fallback for unsummarized sessions\n(see app/session_search.py), so recall is complete without a global FTS\nindex or a forced re-summarize. Reads only the caller's files; the heavy\nIO runs in a thread. Degrades to an empty result on error.\n\nContract v2 body: `{query, scope?, project_ids?, session_ids?,\ncontext_session_id?, limit?, before?, after?}`. `context_session_id` is\nsupplied by Core from ToolContext and is never a model-facing argument.","operationId":"memory_session_search_api_memory_session_search_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Memory Session Search Api Memory Session Search Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/project-memory/source-sessions/{session_id}/conversation-deleted":{"post":{"summary":"Close Project Memory Source Conversation","description":"Discard open project proposals and close accepted-memory provenance.\n\nThe default is intentionally non-destructive: accepted memory remains\nactive but records that its source conversation was deleted. A caller must\nexplicitly request ``delete_project_memory`` to remove those entries.","operationId":"close_project_memory_source_conversation_api_project_memory_source_sessions__session_id__conversation_deleted_post","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectMemorySourceConversationDeletedIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Close Project Memory Source Conversation Api Project Memory Source Sessions  Session Id  Conversation Deleted Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/project-memory/projects/{project_id}":{"get":{"summary":"List Project Memory","operationId":"list_project_memory_api_project_memory_projects__project_id__get","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string","title":"Project Id"}},{"name":"include_inactive","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Inactive"}},{"name":"include_body","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Body"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response List Project Memory Api Project Memory Projects  Project Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/project-memory/projects/{project_id}/imports/project":{"post":{"summary":"Import Project Memory Snapshot","operationId":"import_project_memory_snapshot_api_project_memory_projects__project_id__imports_project_post","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string","title":"Project Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Import Project Memory Snapshot Api Project Memory Projects  Project Id  Imports Project Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/project-memory/projects/{project_id}/{slug}":{"get":{"summary":"Get Project Memory","operationId":"get_project_memory_api_project_memory_projects__project_id___slug__get","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string","title":"Project Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get Project Memory Api Project Memory Projects  Project Id   Slug  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"summary":"Put Project Memory","operationId":"put_project_memory_api_project_memory_projects__project_id___slug__put","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string","title":"Project Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Put Project Memory Api Project Memory Projects  Project Id   Slug  Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Delete Project Memory","operationId":"delete_project_memory_api_project_memory_projects__project_id___slug__delete","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string","title":"Project Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Delete Project Memory Api Project Memory Projects  Project Id   Slug  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/project-memory/projects/{project_id}/{slug}/user-confirmed":{"put":{"summary":"Put User Confirmed Project Memory","description":"Persist an explicit edit made in the interactive Project PI editor.\n\nThis route is intentionally unavailable to process tokens.  A click on\n``Save`` in the PI editor is already the user's review decision, so making\nthe user review the same edit a second time is both confusing and lossy.\nAI/session-derived changes continue to use ``memory_proposals`` and cannot\nreach this endpoint.","operationId":"put_user_confirmed_project_memory_api_project_memory_projects__project_id___slug__user_confirmed_put","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string","title":"Project Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Put User Confirmed Project Memory Api Project Memory Projects  Project Id   Slug  User Confirmed Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Delete User Confirmed Project Memory","description":"Interactive counterpart of the ``user-confirmed`` PUT: an explicit\nRemove click in the PI editor (behind its own confirm dialog) IS the\nuser's review decision, so it may delete protected user-authored\nentries directly. Mirrors the PUT's guards exactly: interactive user\ntokens only (no process tokens — agent-originated deletions still go\nthrough ``memory_proposals``), the four structured PI slugs only, and\nthe live-hash If-Match check still applies.","operationId":"delete_user_confirmed_project_memory_api_project_memory_projects__project_id___slug__user_confirmed_delete","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string","title":"Project Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Delete User Confirmed Project Memory Api Project Memory Projects  Project Id   Slug  User Confirmed Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/project-memory/projects/{project_id}/{slug}/supersede":{"post":{"summary":"Supersede Project Memory","operationId":"supersede_project_memory_api_project_memory_projects__project_id___slug__supersede_post","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string","title":"Project Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Supersede Project Memory Api Project Memory Projects  Project Id   Slug  Supersede Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/memory/consolidate":{"post":{"summary":"Consolidate Memory","description":"Run the manual consolidation pass (all tiers, NOT plan-gated) over\nthe user's own active notes and stage the proposals. Returns the staged\nproposals so the SPA can open the review pane immediately.","operationId":"consolidate_memory_api_memory_consolidate_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Consolidate Memory Api Memory Consolidate Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/memory/proposals":{"get":{"summary":"List Memory Proposals","description":"List the user's proposals (default status=pending). Each carries\n`current_body`/`current_hash` of the live target slug so the SPA can\nrender a before/after diff.\n\n`?status=` filters by status ('all' = any status). `?session_id=`\nscopes to one conversation's session-origin proposals (the\n\"为当前对话生成记忆\" review pane) — both filters compose, so the SPA\nasks for `?session_id=<id>&status=pending` to drive the red-dot count.","operationId":"list_memory_proposals_api_memory_proposals_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response List Memory Proposals Api Memory Proposals Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/memory/proposals/submit":{"post":{"summary":"Submit Memory Proposals","description":"Stage kernel-submitted session-memory proposals (CONTRACT SERVER §).\n\nThe all-tier \"为当前对话生成记忆\" button has omicos-core run the user's\nOWN active model over ONE conversation and POST the result here using\nthe SAME process/user auth as `PUT /api/memory`. ALL-TIER: there is NO\nPro gate (the button spends the user's own model/credits; only the\nnightly dream worker is Pro). The kernel has already dropped NOOPs and\napplied the anti-hallucination gate.\n\nBody: ``{session_id, origin:\"session\", proposals:[{op, target_slug,\nproposed_title, proposed_body?, old_string?, new_string?, rationale,\nimportance, provenance}]}``. Each item is validated (op enum, slug\nregex, 64 KiB body cap); the WHOLE batch is rejected atomically on the\nfirst bad item. ``prev_hash`` is captured per target slug at insert time\n(optimistic concurrency on later approve). Rows land status='pending'.\nReturns ``{inserted: n, proposals:[...]}``.","operationId":"submit_memory_proposals_api_memory_proposals_submit_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Submit Memory Proposals Api Memory Proposals Submit Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/project-memory/projects/{project_id}/reconcile":{"post":{"summary":"Reconcile Project Memory Proposals","description":"Background-maintenance endpoint used before rendering the PI queue.","operationId":"reconcile_project_memory_proposals_api_project_memory_projects__project_id__reconcile_post","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string","title":"Project Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Reconcile Project Memory Proposals Api Project Memory Projects  Project Id  Reconcile Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/memory/proposals/{proposal_id}/undo-auto":{"post":{"summary":"Undo Auto Applied Project Proposal","description":"Undo an unchanged automatically added project fact/artifact.","operationId":"undo_auto_applied_project_proposal_api_memory_proposals__proposal_id__undo_auto_post","parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"integer","title":"Proposal Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Undo Auto Applied Project Proposal Api Memory Proposals  Proposal Id  Undo Auto Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/memory/proposals/{proposal_id}/approve":{"post":{"summary":"Approve Memory Proposal","description":"Approve + apply one proposal atomically.\n\nAn interactive user may optionally submit ``edited_title`` and\n``edited_body`` in the same request.  The reviewed text and the applied\ntext then commit in one transaction; process tokens may approve an\nalready-reviewed payload (for the confirmed PI-assistant flow) but cannot\nrewrite it while approving.","operationId":"approve_memory_proposal_api_memory_proposals__proposal_id__approve_post","parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"integer","title":"Proposal Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Approve Memory Proposal Api Memory Proposals  Proposal Id  Approve Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/memory/proposals/{proposal_id}/reject":{"post":{"summary":"Reject Memory Proposal","description":"Reject a proposal — status='rejected', nothing written.","operationId":"reject_memory_proposal_api_memory_proposals__proposal_id__reject_post","parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"integer","title":"Proposal Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Reject Memory Proposal Api Memory Proposals  Proposal Id  Reject Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/memory/proposals/apply_all":{"post":{"summary":"Apply All Memory Proposals","description":"Batch-apply the given proposal ids in ONE transaction. Any 409\nneeds_rebase (or a mid-batch failure) rolls the whole batch back so a\npartial apply can never leave the queue inconsistent.\n\nApplies in `id ASC` (staging) order regardless of the order `ids` were\nsubmitted in. A single dream pass can stage an `add` for a brand-new\nslug followed by an `update` against that same in-pass slug (see the\nsame-pass slug reservation in memory_consolidate.py's `_run`) — the\nupdate's target only exists once the add is applied first, and staging\nalways assigns the add a lower id (later batches see earlier batches'\nreservations, never the reverse). Trusting the client's array order\ninstead let a UI that lists proposals update-before-add send that pair\nreversed, 409 on the very first item, and roll back the WHOLE batch —\nso \"approve all\" could never succeed even though every individual\nproposal was independently applicable in the right order.","operationId":"apply_all_memory_proposals_api_memory_proposals_apply_all_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Apply All Memory Proposals Api Memory Proposals Apply All Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/memory/dream/trigger":{"post":{"summary":"Trigger Dream","description":"Queue a dream pass for the caller — sets dream_dirty=1 (and\nscope_session_id for an on-demand single-session \"dream this project\nnow\"). The background omicos-dreamer worker claims it; this never runs\nthe LLM inline. Returns {queued:true}.","operationId":"trigger_dream_api_memory_dream_trigger_post","parameters":[{"name":"args","in":"query","required":true,"schema":{"title":"Args"}},{"name":"kwargs","in":"query","required":true,"schema":{"title":"Kwargs"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Trigger Dream Api Memory Dream Trigger Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/memory/migration/status":{"get":{"summary":"Memory Migration Status","description":"Should the SPA offer the migration dialog? Never raises for non-eligible\nusers — returns eligible:false so a normal (community/plus, already-migrated,\nor nothing-to-retire) account just doesn't see the prompt.","operationId":"memory_migration_status_api_memory_migration_status_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Memory Migration Status Api Memory Migration Status Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/memory/migrate":{"post":{"summary":"Memory Migrate","description":"dry_run → the exact retire/keep plan (no mutation); execute → soft-\ninvalidate the gate-failing dream notes + reject stale pending dream\nproposals + queue regeneration. 404 when the flag is off.","operationId":"memory_migrate_api_memory_migrate_post","parameters":[{"name":"args","in":"query","required":true,"schema":{"title":"Args"}},{"name":"kwargs","in":"query","required":true,"schema":{"title":"Kwargs"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MemoryMigrateIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Memory Migrate Api Memory Migrate Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/memory/dream/status":{"get":{"summary":"Dream Status","description":"The dreaming state for the rail badge + status pill: derived state,\npending proposal count, cadence stamps, and the KIND_MEMORY_DREAM quota.","operationId":"dream_status_api_memory_dream_status_get","parameters":[{"name":"args","in":"query","required":true,"schema":{"title":"Args"}},{"name":"kwargs","in":"query","required":true,"schema":{"title":"Kwargs"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Dream Status Api Memory Dream Status Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/memory/dream/settings":{"get":{"summary":"Get Dream Settings","description":"The user's dreaming preferences (opt-in toggle + tunables).","operationId":"get_dream_settings_api_memory_dream_settings_get","parameters":[{"name":"args","in":"query","required":true,"schema":{"title":"Args"}},{"name":"kwargs","in":"query","required":true,"schema":{"title":"Kwargs"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get Dream Settings Api Memory Dream Settings Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"summary":"Put Dream Settings","description":"Update dreaming preferences. Partial — omitted keys keep their stored\nvalue (COALESCE). idle_minutes / max_sessions are clamped to sane bounds.","operationId":"put_dream_settings_api_memory_dream_settings_put","parameters":[{"name":"args","in":"query","required":true,"schema":{"title":"Args"}},{"name":"kwargs","in":"query","required":true,"schema":{"title":"Kwargs"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Put Dream Settings Api Memory Dream Settings Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/memory/{slug}":{"get":{"summary":"Get Memory","operationId":"get_memory_api_memory__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get Memory Api Memory  Slug  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"summary":"Put Memory","operationId":"put_memory_api_memory__slug__put","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Put Memory Api Memory  Slug  Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Delete Memory","operationId":"delete_memory_api_memory__slug__delete","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Delete Memory Api Memory  Slug  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/files":{"get":{"summary":"List User Files","operationId":"list_user_files_api_files_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response List User Files Api Files Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"summary":"Upload User File","operationId":"upload_user_file_api_files_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_user_file_api_files_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Upload User File Api Files Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/files/usage":{"get":{"summary":"Get User Files Usage","operationId":"get_user_files_usage_api_files_usage_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get User Files Usage Api Files Usage Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/usage/trajectory_llm":{"get":{"summary":"Get Trajectory Llm Usage","description":"Backend-paid LLM call meter for the trajectory summarizer.\nReturned shape mirrors `/api/files/usage` so the Profile page can\nrender both with the same progress-bar component. Used and limit\nare zeroed at the start of each calendar month.","operationId":"get_trajectory_llm_usage_api_usage_trajectory_llm_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get Trajectory Llm Usage Api Usage Trajectory Llm Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/llm/cloud/usage":{"get":{"summary":"Get Cloud Llm Usage","description":"Daily free-tier token meter for the settings panel. Same shape the\nSPA (user token) and omicos-core (process token) both read.","operationId":"get_cloud_llm_usage_api_llm_cloud_usage_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get Cloud Llm Usage Api Llm Cloud Usage Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/llm/cloud/chat/completions":{"post":{"summary":"Post Cloud Llm Chat Completions","description":"OpenAI-compatible chat-completions proxy over the shared key. Auth\naccepts a user OR process token (omicos-core calls with the latter).\nQuota is checked BEFORE proxying so a failed upstream call can't burn\nbudget; tokens are recorded AFTER the call inside cloud_llm.","operationId":"post_cloud_llm_chat_completions_api_llm_cloud_chat_completions_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/usage/record":{"post":{"summary":"Post Usage Record","description":"Batch-ingest per-LLM-call audit rows. Idempotent: UNIQUE on\n(user_id, session_id, turn_id, llm_call_index) means a retry of\nan already-stored row is a silent no-op. Returns counters so the\nclient can confirm and (in future) prune its local jsonl.","operationId":"post_usage_record_api_usage_record_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","title":"Payload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Post Usage Record Api Usage Record Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/usage/summary":{"get":{"summary":"Get Usage Summary","description":"Activity-card aggregates: per-bucket totals + grand totals for\nthe requested time window. `period` ∈ {24h, 7d, 1m, 3m, all}.\n`group_by` ∈ {model, provider, agent}. Buckets sorted by\ntotal_tokens DESC so the UI can take top-N directly.","operationId":"get_usage_summary_api_usage_summary_get","parameters":[{"name":"period","in":"query","required":false,"schema":{"type":"string","default":"1m","title":"Period"}},{"name":"group_by","in":"query","required":false,"schema":{"type":"string","default":"model","title":"Group By"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get Usage Summary Api Usage Summary Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/usage/daily":{"get":{"summary":"Get Usage Daily","description":"Per-UTC-day token activity for the profile dashboard: calendar\nheatmap, current/longest streaks, and the lifetime token total.\n`days` ∈ [1, 730] (default 365).","operationId":"get_usage_daily_api_usage_daily_get","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":365,"title":"Days"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get Usage Daily Api Usage Daily Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/usage/logs":{"get":{"summary":"Get Usage Logs","description":"Per-call detail, cursor-paginated (DESC timestamp). `cursor`\nis the previous page's last `timestamp_ms`; omit for page 1.\nOptional `session_id` filter for thread drill-down.","operationId":"get_usage_logs_api_usage_logs_get","parameters":[{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Cursor"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"session_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Session Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get Usage Logs Api Usage Logs Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/files/public/{sha256}":{"get":{"summary":"Download Public Blob","description":"Unauthenticated read of any blob whose sha is referenced by at\nleast one non-deleted user_files row with is_public=1. Used by the\ncloud chat surface to render `![](url)` for figures the model\ngenerated. The CDN-friendly URL has no user-id in it because\ncontent-addressed: same image uploaded by two users is the same\nblob and the same URL.","operationId":"download_public_blob_api_files_public__sha256__get","parameters":[{"name":"sha256","in":"path","required":true,"schema":{"type":"string","title":"Sha256"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/files/{file_id}/visibility":{"post":{"summary":"Set File Visibility","description":"Toggle is_public on an existing file the user owns. Body:\n{\"is_public\": true|false}. Used when the user retroactively wants\nto share (or hide) a previously-uploaded figure.","operationId":"set_file_visibility_api_files__file_id__visibility_post","parameters":[{"name":"file_id","in":"path","required":true,"schema":{"type":"string","title":"File Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","title":"Payload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Set File Visibility Api Files  File Id  Visibility Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/files/{file_id}":{"get":{"summary":"Download User File","operationId":"download_user_file_api_files__file_id__get","parameters":[{"name":"file_id","in":"path","required":true,"schema":{"type":"string","title":"File Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Delete User File","description":"Soft delete — flips deleted_at and decrements ref_count.\nA future janitor task will GC blobs whose ref_count drops to 0\nand prune the soft-deleted rows after some grace period.","operationId":"delete_user_file_api_files__file_id__delete","parameters":[{"name":"file_id","in":"path","required":true,"schema":{"type":"string","title":"File Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Delete User File Api Files  File Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/processes/{process_id}/conversations/{session_id}/chat/stream":{"post":{"summary":"Process Conversation Chat Stream","operationId":"process_conversation_chat_stream_api_processes__process_id__conversations__session_id__chat_stream_post","parameters":[{"name":"process_id","in":"path","required":true,"schema":{"type":"string","title":"Process Id"}},{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/threads":{"post":{"summary":"Create Thread","operationId":"create_thread_api_threads_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ThreadIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Create Thread Api Threads Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"summary":"List Threads","operationId":"list_threads_api_threads_get","parameters":[{"name":"process_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Process Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response List Threads Api Threads Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/threads/{thread_id}/messages":{"get":{"summary":"List Messages","operationId":"list_messages_api_threads__thread_id__messages_get","parameters":[{"name":"thread_id","in":"path","required":true,"schema":{"type":"string","title":"Thread Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response List Messages Api Threads  Thread Id  Messages Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/threads/{thread_id}/chat/stream":{"post":{"summary":"Chat Stream","operationId":"chat_stream_api_threads__thread_id__chat_stream_post","parameters":[{"name":"thread_id","in":"path","required":true,"schema":{"type":"string","title":"Thread Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"AllotmentIn":{"properties":{"domain":{"type":"string","title":"Domain"},"plan_code":{"type":"string","title":"Plan Code"},"seats_total":{"type":"integer","title":"Seats Total"},"trial_quota":{"type":"integer","title":"Trial Quota","default":0}},"type":"object","required":["domain","plan_code","seats_total"],"title":"AllotmentIn"},"AnnotationIn":{"properties":{"record_uuid":{"type":"string","title":"Record Uuid"},"body":{"type":"string","title":"Body"},"severity":{"type":"string","title":"Severity","default":"info"}},"type":"object","required":["record_uuid","body"],"title":"AnnotationIn"},"AppleRepairIn":{"properties":{"transactionId":{"type":"string","title":"Transactionid"}},"type":"object","required":["transactionId"],"title":"AppleRepairIn"},"AppleVerifyIn":{"properties":{"signedTransactionInfo":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Signedtransactioninfo"},"jws":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Jws"},"receiptData":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Receiptdata"},"transactionId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Transactionid"}},"type":"object","title":"AppleVerifyIn"},"BindEmailConfirmIn":{"properties":{"email":{"type":"string","title":"Email"},"code":{"type":"string","title":"Code"}},"type":"object","required":["email","code"],"title":"BindEmailConfirmIn"},"BindEmailSendIn":{"properties":{"email":{"type":"string","title":"Email"},"language":{"type":"string","title":"Language","default":"zh"}},"type":"object","required":["email"],"title":"BindEmailSendIn"},"BindPhoneConfirmIn":{"properties":{"phone":{"type":"string","title":"Phone"},"code":{"type":"string","title":"Code"}},"type":"object","required":["phone","code"],"title":"BindPhoneConfirmIn"},"BindPhoneSendIn":{"properties":{"phone":{"type":"string","title":"Phone"}},"type":"object","required":["phone"],"title":"BindPhoneSendIn"},"Body_upload_figure_blob_api_trajectories__session_id__figures_upload_post":{"properties":{"file":{"type":"string","format":"binary","title":"File"},"original":{"anyOf":[{"type":"string","format":"binary"},{"type":"null"}],"title":"Original"},"canonical_path":{"type":"string","title":"Canonical Path"},"mime":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mime"},"compressed":{"type":"boolean","title":"Compressed","default":false}},"type":"object","required":["file","canonical_path"],"title":"Body_upload_figure_blob_api_trajectories__session_id__figures_upload_post"},"Body_upload_image_for_chat_api_user_settings_image_host_upload_post":{"properties":{"file":{"type":"string","format":"binary","title":"File"}},"type":"object","required":["file"],"title":"Body_upload_image_for_chat_api_user_settings_image_host_upload_post"},"Body_upload_user_file_api_files_post":{"properties":{"file":{"type":"string","format":"binary","title":"File"},"path":{"type":"string","title":"Path"},"is_public":{"type":"string","title":"Is Public","default":"0"}},"type":"object","required":["file","path"],"title":"Body_upload_user_file_api_files_post"},"BootstrapIn":{"properties":{"claim_token":{"type":"string","title":"Claim Token"}},"type":"object","required":["claim_token"],"title":"BootstrapIn"},"CelltypeMarkersResponse":{"properties":{"scope":{"additionalProperties":{"anyOf":[{"type":"string"},{"type":"null"}]},"type":"object","title":"Scope"},"n_requested":{"type":"integer","title":"N Requested"},"n_resolved":{"type":"integer","title":"N Resolved"},"templates":{"items":{"$ref":"#/components/schemas/CelltypeTemplateOut"},"type":"array","title":"Templates"}},"type":"object","required":["scope","n_requested","n_resolved","templates"],"title":"CelltypeMarkersResponse"},"CelltypeTemplateMarkerOut":{"properties":{"gene":{"type":"string","title":"Gene"},"n_pmid":{"type":"integer","title":"N Pmid"},"pmids":{"items":{"type":"string"},"type":"array","title":"Pmids"}},"type":"object","required":["gene","n_pmid","pmids"],"title":"CelltypeTemplateMarkerOut"},"CelltypeTemplateOut":{"properties":{"cell_name":{"type":"string","title":"Cell Name"},"cl_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cl Id"},"positive":{"items":{"$ref":"#/components/schemas/CelltypeTemplateMarkerOut"},"type":"array","title":"Positive"},"negative":{"items":{"$ref":"#/components/schemas/CelltypeTemplateMarkerOut"},"type":"array","title":"Negative"}},"type":"object","required":["cell_name","positive","negative"],"title":"CelltypeTemplateOut"},"ChatIn":{"properties":{"message":{"type":"string","title":"Message"},"config":{"type":"object","title":"Config","default":{}}},"type":"object","required":["message"],"title":"ChatIn"},"ChatProjectAssignmentInput":{"properties":{"project_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project Id"},"expected_revision":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Expected Revision"}},"type":"object","title":"ChatProjectAssignmentInput"},"ChatProjectDeleteInput":{"properties":{"expected_revision":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Expected Revision"}},"type":"object","title":"ChatProjectDeleteInput"},"ChatProjectImportInput":{"properties":{"projects":{"items":{"$ref":"#/components/schemas/ChatProjectImportProject"},"type":"array","title":"Projects"},"assignments":{"additionalProperties":{"type":"string"},"type":"object","title":"Assignments"},"expected_revision":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Expected Revision"}},"type":"object","title":"ChatProjectImportInput"},"ChatProjectImportProject":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"created_at":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Created At"}},"type":"object","required":["id","name"],"title":"ChatProjectImportProject"},"ChatProjectInput":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id"},"name":{"type":"string","title":"Name"},"created_at":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Created At"},"expected_revision":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Expected Revision"}},"type":"object","required":["name"],"title":"ChatProjectInput"},"ChatProjectRenameInput":{"properties":{"name":{"type":"string","title":"Name"},"expected_revision":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Expected Revision"}},"type":"object","required":["name"],"title":"ChatProjectRenameInput"},"CheckoutIn":{"properties":{"plan_code":{"type":"string","title":"Plan Code"},"period":{"type":"string","title":"Period"},"provider":{"type":"string","title":"Provider"},"domain":{"type":"string","title":"Domain","default":"omics"},"seats":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Seats"},"lab_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Lab Id"},"lab_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Lab Name"}},"type":"object","required":["plan_code","period","provider"],"title":"CheckoutIn"},"CommandIn":{"properties":{"type":{"type":"string","title":"Type"},"payload":{"type":"object","title":"Payload","default":{}}},"type":"object","required":["type"],"title":"CommandIn"},"ConversationDeletionsIn":{"properties":{"session_ids":{"items":{"type":"string"},"type":"array","title":"Session Ids","default":[]}},"type":"object","title":"ConversationDeletionsIn"},"ConversationModelIn":{"properties":{"provider":{"type":"string","title":"Provider","default":""},"model":{"type":"string","title":"Model","default":""}},"type":"object","title":"ConversationModelIn"},"CreateLabIn":{"properties":{"name":{"type":"string","title":"Name"},"tier":{"type":"string","title":"Tier","default":"lab_basic"}},"type":"object","required":["name"],"title":"CreateLabIn"},"CreateResellerIn":{"properties":{"name":{"type":"string","title":"Name"},"domain_scope":{"items":{"type":"string"},"type":"array","title":"Domain Scope","default":["omics"]},"region":{"type":"string","title":"Region","default":""},"contact_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Email"}},"type":"object","required":["name"],"title":"CreateResellerIn"},"DeleteAccountIn":{"properties":{"password":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Password"},"code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Code"}},"type":"object","title":"DeleteAccountIn"},"EmailLoginIn":{"properties":{"email":{"type":"string","title":"Email"},"code":{"type":"string","title":"Code"}},"type":"object","required":["email","code"],"title":"EmailLoginIn"},"EmailSendIn":{"properties":{"email":{"type":"string","title":"Email"},"language":{"type":"string","title":"Language","default":"zh"}},"type":"object","required":["email"],"title":"EmailSendIn"},"ForkIn":{"properties":{"head_uuid":{"type":"string","title":"Head Uuid"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"}},"type":"object","required":["head_uuid"],"title":"ForkIn"},"GrantSeatIn":{"properties":{"plan_code":{"type":"string","title":"Plan Code"},"domain":{"type":"string","title":"Domain","default":"omics"},"until":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Until"}},"type":"object","required":["plan_code"],"title":"GrantSeatIn"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HeartbeatIn":{"properties":{"process_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Process Id"},"active":{"type":"boolean","title":"Active","default":false}},"type":"object","title":"HeartbeatIn"},"ImageHostConfigIn":{"properties":{"provider":{"type":"string","pattern":"^[a-z][a-z0-9_]*$","title":"Provider","default":"github"},"config":{"type":"object","title":"Config"},"active_provider":{"anyOf":[{"type":"string","pattern":"^(off|github|cloud)$"},{"type":"null"}],"title":"Active Provider"}},"type":"object","title":"ImageHostConfigIn","description":"Write-side payload. provider determines which fields under\n`config` are honored. Unknown provider raises 400.\n\n`active_provider` is the user's chosen routing for figure uploads:\n  - 'off'    — no image hosting; system prompt skips the markdown\n               image directive entirely.\n  - 'github' — use the saved github config (PicGo-style).\n  - 'cloud'  — use /api/files cloud blob (paid only).\nOptional in the write payload — if omitted, the existing value is\npreserved. Lets the SPA send only the fields it changed."},"ImageHostTestIn":{"properties":{"inline":{"type":"boolean","title":"Inline","default":true},"content_b64":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content B64"},"filename":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Filename"}},"type":"object","title":"ImageHostTestIn","description":"Test-upload payload. `inline=true` means use the bundled 1×1 PNG;\notherwise content_b64 is the uploader's payload (raw bytes b64'd).\nUsed by the Keys page's \"Test upload\" button."},"IssueInvoiceIn":{"properties":{"invoice_no":{"type":"string","title":"Invoice No"}},"type":"object","required":["invoice_no"],"title":"IssueInvoiceIn"},"JoinLabIn":{"properties":{"invitation_code":{"type":"string","title":"Invitation Code"}},"type":"object","required":["invitation_code"],"title":"JoinLabIn"},"LoginIn":{"properties":{"email":{"type":"string","title":"Email"},"password":{"type":"string","title":"Password"}},"type":"object","required":["email","password"],"title":"LoginIn"},"LuriaCreditsCheckoutIn":{"properties":{"pack":{"type":"string","title":"Pack"},"provider":{"type":"string","title":"Provider","default":"wechat"}},"type":"object","required":["pack"],"title":"LuriaCreditsCheckoutIn"},"MarkerHitOut":{"properties":{"gene":{"type":"string","title":"Gene"},"z":{"type":"number","title":"Z"},"polarity":{"type":"string","title":"Polarity"},"spec":{"type":"number","title":"Spec"},"contribution":{"type":"number","title":"Contribution"},"n_pmid":{"type":"integer","title":"N Pmid"},"pmids":{"items":{"type":"string"},"type":"array","title":"Pmids"}},"type":"object","required":["gene","z","polarity","spec","contribution","n_pmid","pmids"],"title":"MarkerHitOut"},"MemoryMigrateIn":{"properties":{"mode":{"type":"string","title":"Mode","default":"dry_run"}},"type":"object","title":"MemoryMigrateIn"},"PartnerProvisionIn":{"properties":{"phone":{"type":"string","title":"Phone"},"external_order_id":{"type":"string","title":"External Order Id"},"idempotency_key":{"type":"string","title":"Idempotency Key"},"plan_code":{"type":"string","title":"Plan Code","default":"pro"},"domain":{"type":"string","title":"Domain","default":"omics"},"period_months":{"type":"integer","title":"Period Months","default":12}},"type":"object","required":["phone","external_order_id","idempotency_key"],"title":"PartnerProvisionIn"},"PatchLabIn":{"properties":{"tier":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tier"},"seat_quota":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Seat Quota"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},"type":"object","title":"PatchLabIn"},"PatchResellerIn":{"properties":{"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"domain_scope":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Domain Scope"},"commission_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Commission Rate"}},"type":"object","title":"PatchResellerIn"},"PatchUserIn":{"properties":{"plan_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Plan Code"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"suspended":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Suspended"},"features":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Features"}},"type":"object","title":"PatchUserIn"},"PlanChangeIn":{"properties":{"plan_code":{"type":"string","title":"Plan Code"}},"type":"object","required":["plan_code"],"title":"PlanChangeIn"},"PlanIn":{"properties":{"request":{"type":"string","title":"Request"},"conversation_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Conversation Id"}},"type":"object","required":["request"],"title":"PlanIn"},"ProfileIn":{"properties":{"name":{"type":"string","title":"Name","default":""},"display_name":{"type":"string","title":"Display Name","default":""},"full_name":{"type":"string","title":"Full Name","default":""},"institution":{"type":"string","title":"Institution","default":""},"research_area":{"type":"string","title":"Research Area","default":""},"usage_purpose":{"type":"string","title":"Usage Purpose","default":""},"title":{"type":"string","title":"Title","default":""}},"type":"object","title":"ProfileIn"},"ProjectMemorySourceConversationDeletedIn":{"properties":{"delete_project_memory":{"type":"boolean","title":"Delete Project Memory","default":false}},"type":"object","title":"ProjectMemorySourceConversationDeletedIn"},"RecordInvoiceIn":{"properties":{"order_nos":{"items":{"type":"string"},"type":"array","title":"Order Nos"},"buyer_type":{"type":"string","title":"Buyer Type"},"buyer_title":{"type":"string","title":"Buyer Title"},"buyer_tax_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Buyer Tax Id"},"buyer_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Buyer Email"},"invoice_kind":{"type":"string","title":"Invoice Kind","default":"normal"},"buyer_address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Buyer Address"},"buyer_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Buyer Phone"},"buyer_bank":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Buyer Bank"},"buyer_bank_account":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Buyer Bank Account"},"content":{"type":"string","title":"Content","default":""},"remark":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Remark"},"status":{"type":"string","title":"Status","default":"pending"},"invoice_no":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Invoice No"}},"type":"object","required":["order_nos","buyer_type","buyer_title"],"title":"RecordInvoiceIn"},"ReferralClaimIn":{"properties":{"code":{"type":"string","title":"Code"}},"type":"object","required":["code"],"title":"ReferralClaimIn"},"RegisterIn":{"properties":{"email":{"type":"string","title":"Email"},"password":{"type":"string","title":"Password"},"name":{"type":"string","title":"Name","default":""},"display_name":{"type":"string","title":"Display Name","default":""},"full_name":{"type":"string","title":"Full Name","default":""},"institution":{"type":"string","title":"Institution","default":""},"research_area":{"type":"string","title":"Research Area","default":""},"usage_purpose":{"type":"string","title":"Usage Purpose","default":""},"title":{"type":"string","title":"Title","default":""},"language":{"type":"string","title":"Language","default":"zh"},"region":{"type":"string","title":"Region","default":"CN"}},"type":"object","required":["email","password"],"title":"RegisterIn"},"ReleaseManifestIn":{"properties":{"version":{"type":"string","title":"Version"},"notes":{"type":"string","title":"Notes","default":""},"pub_date":{"type":"string","title":"Pub Date","default":""},"platforms":{"type":"object","title":"Platforms","default":{}}},"additionalProperties":true,"type":"object","required":["version"],"title":"ReleaseManifestIn"},"RenameIn":{"properties":{"name":{"type":"string","title":"Name"}},"type":"object","required":["name"],"title":"RenameIn"},"ResellerAdminIn":{"properties":{"email":{"type":"string","title":"Email"},"password":{"type":"string","title":"Password"},"role":{"type":"string","title":"Role","default":"owner"}},"type":"object","required":["email","password"],"title":"ResellerAdminIn"},"ResellerApiKeyIn":{"properties":{"name":{"type":"string","title":"Name","default":"integration"},"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes","default":["customers:provision"]},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At"}},"type":"object","title":"ResellerApiKeyIn"},"ResendActivationIn":{"properties":{"email":{"type":"string","title":"Email"}},"type":"object","required":["email"],"title":"ResendActivationIn"},"ResetPasswordIn":{"properties":{"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"code":{"type":"string","title":"Code"},"new_password":{"type":"string","title":"New Password"}},"type":"object","required":["code","new_password"],"title":"ResetPasswordIn"},"RevokeIn":{"properties":{"domain":{"type":"string","title":"Domain","default":"omics"}},"type":"object","title":"RevokeIn"},"ScoreCandidateOut":{"properties":{"rank":{"type":"integer","title":"Rank"},"cell_name":{"type":"string","title":"Cell Name"},"cl_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cl Id"},"score":{"type":"number","title":"Score"},"score_raw":{"type":"number","title":"Score Raw"},"n_positive_total":{"type":"integer","title":"N Positive Total"},"n_negative_total":{"type":"integer","title":"N Negative Total"},"n_positive_seen":{"type":"integer","title":"N Positive Seen"},"n_negative_seen":{"type":"integer","title":"N Negative Seen"},"template_l2":{"type":"number","title":"Template L2"},"top_supporting":{"items":{"$ref":"#/components/schemas/MarkerHitOut"},"type":"array","title":"Top Supporting"},"top_conflicting":{"items":{"$ref":"#/components/schemas/MarkerHitOut"},"type":"array","title":"Top Conflicting"},"evidence_pmids":{"items":{"type":"string"},"type":"array","title":"Evidence Pmids"}},"type":"object","required":["rank","cell_name","score","score_raw","n_positive_total","n_negative_total","n_positive_seen","n_negative_seen","template_l2","top_supporting","top_conflicting","evidence_pmids"],"title":"ScoreCandidateOut"},"ScoreClusterIn":{"properties":{"cluster_zscores":{"additionalProperties":{"type":"number"},"type":"object","minProperties":1,"title":"Cluster Zscores","description":"Dict of {gene_symbol: cluster_vs_rest_zscore}. Typically the `scores` field of scanpy.tl.rank_genes_groups, restricted to HVGs. Both signs are used by the scorer."},"species":{"type":"string","minLength":1,"title":"Species"},"tissue":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tissue"},"disease":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disease"},"top_n":{"type":"integer","maximum":100.0,"minimum":1.0,"title":"Top N","default":10},"include_negative_markers":{"type":"boolean","title":"Include Negative Markers","default":true}},"type":"object","required":["cluster_zscores","species"],"title":"ScoreClusterIn","description":"Score one cluster's expression signature against the atlas.\n\n`cluster_zscores` is `{gene_symbol: cluster_vs_rest_zscore}` —\none cluster's differential-expression z-score against all other\ncells. The recommended client recipe is scanpy's\n`rank_genes_groups(adata, groupby='leiden', method='wilcoxon')`,\nkeeping only HVGs (~2k-5k genes, ~50 KB JSON). z > 0 = the gene\nis upregulated in this cluster vs rest; z < 0 = downregulated.\n\nDo NOT use \"cluster mean of globally z-scored X\" — that collapses\nto ≈ 0 for clusters that dominate the cohort (the cluster's cells\nARE the population mean it's z-scored against). Cluster-vs-rest\nis the right baseline.\n\nThe server scores each candidate as the L2-normalised inner\nproduct of the cluster's z-vector against a signed marker\ntemplate (+spec for positive markers, -spec for negative markers,\nspec scoped to tissue + species + optional disease). The full\natlas never leaves the server."},"ScoreResponse":{"properties":{"n_genes_supplied":{"type":"integer","title":"N Genes Supplied"},"species":{"type":"string","title":"Species"},"tissue":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tissue"},"disease":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disease"},"n_candidate_celltypes_scanned":{"type":"integer","title":"N Candidate Celltypes Scanned"},"n_rows_scanned":{"type":"integer","title":"N Rows Scanned"},"candidates":{"items":{"$ref":"#/components/schemas/ScoreCandidateOut"},"type":"array","title":"Candidates"}},"type":"object","required":["n_genes_supplied","species","tissue","disease","n_candidate_celltypes_scanned","n_rows_scanned","candidates"],"title":"ScoreResponse"},"SeatChangeIn":{"properties":{"seats":{"type":"integer","title":"Seats"},"provider":{"type":"string","title":"Provider","default":"wechat"}},"type":"object","required":["seats"],"title":"SeatChangeIn","description":"Change a paid lab's seat count. `seats` is the NEW TOTAL (not a\ndelta). Growing beyond the current cap creates a prorated, co-terminating\ntop-up order paid via `provider`; shrinking takes effect immediately with\nno refund (surplus is not credited — the '不退不换' policy)."},"SelfInvoiceIn":{"properties":{"order_nos":{"items":{"type":"string"},"type":"array","title":"Order Nos"},"buyer_type":{"type":"string","title":"Buyer Type"},"buyer_title":{"type":"string","title":"Buyer Title"},"buyer_tax_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Buyer Tax Id"},"buyer_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Buyer Email"},"invoice_kind":{"type":"string","title":"Invoice Kind","default":"normal"},"buyer_address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Buyer Address"},"buyer_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Buyer Phone"},"buyer_bank":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Buyer Bank"},"buyer_bank_account":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Buyer Bank Account"},"remark":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Remark"}},"type":"object","required":["order_nos","buyer_type","buyer_title"],"title":"SelfInvoiceIn","description":"A self-service invoice request.\n\nDeliberately narrower than the admin's `RecordInvoiceIn`: no `status`, no\n`invoice_no` and no `content`. A user may not declare their own invoice\nalready issued, and the content line is fixed (软件服务费) so every invoice\nwe hand to the tax system reads the same."},"SessionIn":{"properties":{"tier":{"type":"string","title":"Tier","default":"cpu"},"conversation_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Conversation Id"},"provider":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider"}},"type":"object","title":"SessionIn"},"SetPasswordIn":{"properties":{"new_password":{"type":"string","title":"New Password"},"current_password":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Current Password"}},"type":"object","required":["new_password"],"title":"SetPasswordIn"},"SmsLoginIn":{"properties":{"phone":{"type":"string","title":"Phone"},"code":{"type":"string","title":"Code"},"language":{"type":"string","title":"Language","default":"zh"}},"type":"object","required":["phone","code"],"title":"SmsLoginIn"},"SmsSendIn":{"properties":{"phone":{"type":"string","title":"Phone"}},"type":"object","required":["phone"],"title":"SmsSendIn"},"SummaryRequestIn":{"properties":{"parent_uuid":{"type":"string","title":"Parent Uuid"},"content":{"type":"string","title":"Content"},"prompt_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Prompt Id"}},"type":"object","required":["parent_uuid","content"],"title":"SummaryRequestIn"},"ThreadIn":{"properties":{"process_id":{"type":"string","title":"Process Id"},"title":{"type":"string","title":"Title","default":"New chat"}},"type":"object","required":["process_id"],"title":"ThreadIn"},"TopUpIn":{"properties":{"credits":{"type":"integer","title":"Credits"}},"type":"object","required":["credits"],"title":"TopUpIn"},"UpdateMemberIn":{"properties":{"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},"type":"object","title":"UpdateMemberIn"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"VerifyOtpIn":{"properties":{"email":{"type":"string","title":"Email"},"code":{"type":"string","title":"Code"}},"type":"object","required":["email","code"],"title":"VerifyOtpIn"},"VisibilityIn":{"properties":{"visibility":{"type":"string","title":"Visibility"}},"type":"object","required":["visibility"],"title":"VisibilityIn"},"VoidInvoiceIn":{"properties":{"remark":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Remark"}},"type":"object","title":"VoidInvoiceIn"},"WechatLoginIn":{"properties":{"code":{"type":"string","title":"Code"}},"type":"object","required":["code"],"title":"WechatLoginIn"},"WechatOAuthIn":{"properties":{"code":{"type":"string","title":"Code"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"}},"type":"object","required":["code"],"title":"WechatOAuthIn"},"WechatQrIn":{"properties":{"state":{"type":"string","title":"State"}},"type":"object","required":["state"],"title":"WechatQrIn"},"_CliApproveBody":{"properties":{"user_code":{"type":"string","title":"User Code"}},"type":"object","required":["user_code"],"title":"_CliApproveBody"},"_CliPollBody":{"properties":{"device_code":{"type":"string","title":"Device Code"}},"type":"object","required":["device_code"],"title":"_CliPollBody"}}}}