{"openapi":"3.1.0","info":{"title":"xhostd public","version":"0.1.0"},"paths":{"/healthz":{"get":{"summary":"Healthz","operationId":"healthz_healthz_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object","title":"Response Healthz Healthz Get"}}}}}}},"/admin/forward-nodes":{"post":{"summary":"Create Forward Node","operationId":"create_forward_node_admin_forward_nodes_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/CreateForwardNodeRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForwardNodeInfo"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"summary":"List Forward Nodes","operationId":"list_forward_nodes_admin_forward_nodes_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":{"$ref":"#/components/schemas/ForwardNodeListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/forward-nodes/{node_id}/enable":{"post":{"summary":"Enable Forward Node","operationId":"enable_forward_node_admin_forward_nodes__node_id__enable_post","parameters":[{"name":"node_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Node Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForwardNodeInfo"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/forward-nodes/{node_id}/disable":{"post":{"summary":"Disable Forward Node","description":"Drain the node: no NEW allocation lands on it.\n\nExisting allocations keep resolving — draining is a two-step\noperator flow (disable, then unexpose the stragglers), never a\nsilent mass disconnect.","operationId":"disable_forward_node_admin_forward_nodes__node_id__disable_post","parameters":[{"name":"node_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Node Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForwardNodeInfo"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/forward-nodes/{node_id}":{"delete":{"summary":"Remove Forward Node","operationId":"remove_forward_node_admin_forward_nodes__node_id__delete","parameters":[{"name":"node_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Node 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","additionalProperties":{"type":"string"},"title":"Response Remove Forward Node Admin Forward Nodes  Node Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/hosts":{"post":{"summary":"Create Host","operationId":"create_host_admin_hosts_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/CreateHostRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateHostResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"summary":"List Hosts","operationId":"list_hosts_admin_hosts_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":{"$ref":"#/components/schemas/ListHostsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/hosts/{host_id}/drain":{"post":{"summary":"Drain Host","operationId":"drain_host_admin_hosts__host_id__drain_post","parameters":[{"name":"host_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Host 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","additionalProperties":true,"title":"Response Drain Host Admin Hosts  Host Id  Drain Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/hosts/{host_id}/quarantine":{"post":{"summary":"Quarantine Host","description":"Quarantine a host (admin): incident state, excluded everywhere.\n\nAllowed from any state and idempotent. The only refusal is an\nin-flight user move touching this host — quarantining mid-move\nwould strand the move halfway.","operationId":"quarantine_host_admin_hosts__host_id__quarantine_post","parameters":[{"name":"host_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Host 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","additionalProperties":true,"title":"Response Quarantine Host Admin Hosts  Host Id  Quarantine Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/hosts/{host_id}/reactivate":{"post":{"summary":"Reactivate Host","description":"Release a quarantined or draining host back to ``active`` (admin).\n\nOne symmetric route for both gate-blocked states; a no-op (still\n200) when the host is already ``active``. A ``pending`` host has\nnever enrolled — activation, not reactivation, is its path.\n\nReactivation asserts the physical box behind the row is alive and\nunchanged: refused (409) when the heartbeat is stale or absent,\nbecause a wiped/replaced box must go through delete + fresh\nenrollment, never a bare state flip. Any live box heartbeats — via\nthe boot gate while passive or the agent while serving — so\nfreshness is always available when reactivation is legitimate.\n``force=true`` is the explicit operator override.","operationId":"reactivate_host_admin_hosts__host_id__reactivate_post","parameters":[{"name":"host_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Host Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/ReactivateHostRequest"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Reactivate Host Admin Hosts  Host Id  Reactivate Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/hosts/{host_id}/activate":{"post":{"summary":"Activate Host","description":"Release a ``commissioning`` host to ``active`` (admin).\n\nOne-shot commissioning -> active gate: called by ``add-worker.sh``'s\nrelease step after every post-check passes (xhost-git, the\ngit-over-SSH hop, negative port scans), and\nby the operator via ``xhostctl admin host activate`` if add-worker\ndied between the token exchange and its release step. No\nfreshness guard: add-worker step 9 has just verified the heartbeat.\n\n- ``commissioning`` -> ``active`` (OK).\n- ``pending`` -> 400: no enrollment yet; run add-worker first.\n- ``active`` -> 409: loud on stale re-run rather than a silent\n  success (the operator should notice the cell was already\n  released).\n- ``draining`` / ``quarantined`` -> 400: those are gate-release\n  states owned by the reactivate route, not commissioning.\n\nRegisters the host's agent in the same transaction, because\n``commissioning`` is outside the serving set the boot populator\nreads. add-worker.sh normally calls this seconds after the token\nexchange registered one, but a control-plane restart in between —\nor an operator running `xhostctl admin host activate` later, which\nthis route exists for — leaves a serving host with no agent.","operationId":"activate_host_admin_hosts__host_id__activate_post","parameters":[{"name":"host_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Host 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","additionalProperties":true,"title":"Response Activate Host Admin Hosts  Host Id  Activate Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/hosts/{host_id}":{"patch":{"summary":"Update Host","description":"Repoint a host's git endpoint / set-or-clear its machine class (admin).\n\nOnly the fields present in the request body are patched, so a\n``machine_class``-only PATCH leaves ``git_endpoint`` untouched and\nvice versa. The git endpoint is NOT NULL: it can be repointed but\nnever cleared (400). A body that names any other field, such as the\nretired ``db_endpoint``, is refused (422) and changes nothing. No\nPATCH moves ``hosts.overlay_ip``.","operationId":"update_host_admin_hosts__host_id__patch","parameters":[{"name":"host_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Host 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/UpdateHostRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HostInfo"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Remove Host","operationId":"remove_host_admin_hosts__host_id__delete","parameters":[{"name":"host_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Host 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","additionalProperties":true,"title":"Response Remove Host Admin Hosts  Host Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/hosts/{host_id}/channels":{"get":{"summary":"Get Host Channels","description":"Return channels placed on a specific host (admin only).","operationId":"get_host_channels_admin_hosts__host_id__channels_get","parameters":[{"name":"host_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Host 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","additionalProperties":true,"title":"Response Get Host Channels Admin Hosts  Host Id  Channels Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/users/{user_id}/move":{"post":{"summary":"Move User","operationId":"move_user_admin_users__user_id__move_post","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","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/MoveUserRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/users/{user_id}/move-databases":{"post":{"summary":"Move Databases","operationId":"move_databases_admin_users__user_id__move_databases_post","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","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/MoveDatabasesRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/jobs/{job_id}":{"get":{"summary":"Get Job","operationId":"get_job_admin_jobs__job_id__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Job Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/jobs/{job_id}/resume":{"post":{"summary":"Resume Job","operationId":"resume_job_admin_jobs__job_id__resume_post","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Job Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/ResumeJobRequest"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/jobs/{job_id}/abort":{"post":{"summary":"Abort Job","operationId":"abort_job_admin_jobs__job_id__abort_post","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Job Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/pg-instances":{"post":{"summary":"Create Pg Instance","description":"Create a bounded instance on a host (admin). Answers the ``Command``.","operationId":"create_pg_instance_admin_pg_instances_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/CreateInstanceRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Create Pg Instance Admin Pg Instances Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"summary":"List Pg Instances","description":"Every instance a host writes, oldest first (admin).","operationId":"list_pg_instances_admin_pg_instances_get","parameters":[{"name":"host_id","in":"query","required":true,"schema":{"type":"string","format":"uuid","title":"Host 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","additionalProperties":true,"title":"Response List Pg Instances Admin Pg Instances Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/pg-commands/{request_id}":{"get":{"summary":"Get Pg Command","description":"One recorded Postgres core command (admin); 404 when none.","operationId":"get_pg_command_admin_pg_commands__request_id__get","parameters":[{"name":"request_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Request 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","additionalProperties":true,"title":"Response Get Pg Command Admin Pg Commands  Request Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/pg-instances/{instance_id}/retry":{"post":{"summary":"Retry Pg Instance Create","description":"Run the create steps again on a ``provisioning`` instance (admin).","operationId":"retry_pg_instance_create_admin_pg_instances__instance_id__retry_post","parameters":[{"name":"instance_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Instance Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/InstanceCommandRequest"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Retry Pg Instance Create Admin Pg Instances  Instance Id  Retry Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/pg-instances/{instance_id}/retire":{"post":{"summary":"Retire Pg Instance","description":"Retire an instance and record its removal from its cell (admin).\n\n:func:`retire_pg_instance_for_admin` holds the refusals and the\nremoval contract.","operationId":"retire_pg_instance_admin_pg_instances__instance_id__retire_post","parameters":[{"name":"instance_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Instance Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/InstanceCommandRequest"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Retire Pg Instance Admin Pg Instances  Instance Id  Retire Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/pg-instances/{instance_id}/remove":{"post":{"summary":"Remove Pg Instance","description":"Remove a retired instance from the named host (admin). Answers the ``Command``.","operationId":"remove_pg_instance_admin_pg_instances__instance_id__remove_post","parameters":[{"name":"instance_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Instance 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/RemoveInstanceRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Remove Pg Instance Admin Pg Instances  Instance Id  Remove Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/pg-instances/{instance_id}/resize":{"post":{"summary":"Resize Pg Instance","description":"Resize an active bounded instance (admin). Answers the ``Command``.\n\n:func:`resize_pg_instance_for_admin` holds the refusals.","operationId":"resize_pg_instance_admin_pg_instances__instance_id__resize_post","parameters":[{"name":"instance_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Instance 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/ResizeInstanceRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Resize Pg Instance Admin Pg Instances  Instance Id  Resize Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/pg-instances/{instance_id}/ha-mode":{"post":{"summary":"Set Pg Instance Ha Mode","description":"Protect an instance or take its protection off (admin).\n\n:func:`set_pg_instance_ha_mode_for_admin` holds the refusals.","operationId":"set_pg_instance_ha_mode_admin_pg_instances__instance_id__ha_mode_post","parameters":[{"name":"instance_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Instance 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/HaModeRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Set Pg Instance Ha Mode Admin Pg Instances  Instance Id  Ha Mode Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/pg-instances/{instance_id}/replica":{"post":{"summary":"Create Pg Replica","description":"Record a standing replica of a protected instance (admin). Answers\nthe ``Command``.\n\n:func:`create_pg_replica_for_admin` holds the gates and the refusals.","operationId":"create_pg_replica_admin_pg_instances__instance_id__replica_post","parameters":[{"name":"instance_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Instance Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/CreateReplicaRequest"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Create Pg Replica Admin Pg Instances  Instance Id  Replica Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"summary":"Get Pg Replica","description":"An instance's ``ha_mode`` and its standing replica (admin).","operationId":"get_pg_replica_admin_pg_instances__instance_id__replica_get","parameters":[{"name":"instance_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Instance 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","additionalProperties":true,"title":"Response Get Pg Replica Admin Pg Instances  Instance Id  Replica Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/pg-instances/{instance_id}/replica/remove":{"post":{"summary":"Remove Pg Replica","description":"Record the removal of an instance's standing replica (admin).\nAnswers the ``Command``.\n\n:func:`remove_pg_replica_for_admin` holds the refusals.","operationId":"remove_pg_replica_admin_pg_instances__instance_id__replica_remove_post","parameters":[{"name":"instance_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Instance Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/RemoveReplicaRequest"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Remove Pg Replica Admin Pg Instances  Instance Id  Replica Remove Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/apps/{app_id}/channels/{channel_id}/rebuild":{"post":{"summary":"Rebuild Channel","operationId":"rebuild_channel_admin_apps__app_id__channels__channel_id__rebuild_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Channel Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RebuildResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/tokens":{"post":{"summary":"Create Token","description":"Mint a token for the caller with the default granted scopes.\n\nA mint never grants more than the caller already holds, so a caller\nthat does not hold the whole default set cannot mint it here (403\n``scope_denied``). The grant is fixed, so the gate is the whole rule\non this route; the remedy is ``POST /credentials``, which grants the\ncaller's own set or less. The check runs before the label check: a\ncaller that cannot mint at all should not learn the label rules.\n\nThe route refuses the reserved console-session label, exactly like\n``routes/internal.py::create_user_token``. A token under that label\nhides from the console token list and escapes the per-label cap.\n\nA mint never outlives the caller either, so the caller's remaining\nlife is the ceiling on ``expires_in`` and the child's expiry when the\nrequest names none. A caller with no expiry keeps the route's\nhistorical behaviour: no ceiling, and no expiry on the child.","operationId":"create_token_tokens_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/CreateTokenRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTokenResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/credentials":{"post":{"summary":"Create Credentials","description":"Mint a unified credential for the caller.\n\nThe grant is variable, so the caller's own set bounds it: this route\ngrants the default scopes the caller holds, or the subset the body\nnames. A general credential therefore mints the nine defaults exactly\nas before, and a narrowed one renews itself instead of widening.\n\nThe caller's remaining life bounds the lifetime the same way. The\nceiling is the earlier of the route's 30 days and that remainder, so\nan over-long ``expires_in`` still reads 400, and a caller with no\nexpiry still reads the 30-day answer.","operationId":"create_credentials_credentials_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/CreateCredentialsRequest"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CredentialsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/tokens/{token_id}":{"delete":{"summary":"Delete Token","operationId":"delete_token_tokens__token_id__delete","parameters":[{"name":"token_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Token Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/ssh-keys":{"post":{"summary":"Create Ssh Key","description":"Register one OpenSSH public key for the caller.\n\nThe route answers 400 for a line the parser refuses, and 409 for a\nfingerprint the table already holds. The unique constraint is\nglobal, so the same key cannot name two accounts.\n\nThe route writes one ``ssh_key.added`` notification and queues one\n``security.ssh_key_added`` mail in the same transaction as the row.\nA coding agent can register a key for the user, so the user must\nlearn about the new key. Both carry the same text: a bell row alone\nreaches nobody on an account no person watches, and the mail leaves\nthe console (design/EMAIL.md). An account that holds no verified\naddress gets the notification alone, because the sink writes no row\nfor an empty recipient.\n\n``api_login`` stores the caller's value. True means the key can also\nmint a bearer token through ``POST /auth/ssh-key``, and the shared\ntext says so. The console sends a checkbox that starts cleared, so a\npasted key stays a git identity unless the person asks for more.\n\nRegistering a key is a mint, so the mint rule gates it. A key\nsatisfies ``repo:*`` by construction (design/AUTH.md), so the route\nrequires ``repo:*``; ``api_login`` additionally requires the whole\ndefault set, because the key goes on to mint that set plus\n``email:bind`` through ``POST /auth/ssh-key``. Either refusal answers\n403 ``scope_denied``.\n\n``api_login`` also needs an ``ssh-ed25519`` key, the one type\n``POST /auth/ssh-key`` accepts. The route answers 400 for any other\ntype, so the stored flag never promises a sign-in the login route\nrefuses. Every other accepted type still registers as a git identity.\n\n``api_login`` then needs one more thing, because that mint carries\n``email:bind`` and a bound address takes the account's console: the\naccount holds a verified address, or the caller's own token carries\n``email:bind``. The second arm is the rule that a key-proved\ncredential can register another key — only the two proof-based mints\nin :mod:`xhostd.routes.registration` grant that scope, so a token\nfrom ``POST /credentials`` or ``POST /tokens`` never holds it, and an\nagent with no address still rotates its own keys. A refusal answers\n409 ``conflict``, the ``POST /me/email-verifications`` shape: nothing\nabout the caller's authorization is wrong, and the account is not\nready.\n\nThe two scope checks run first, before any read of the account, so a\ncaller that fails authorization learns nothing about account state.\n\nThis route carries NO protected-action gate. A gate would close the\none path that lets an agent work without a secret in its context. The\nscope gate is not that gate: a general credential passes it, and only\na narrowed one, which could not reach the repositories itself, is\nrefused.","operationId":"create_ssh_key_ssh_keys_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/CreateSshKeyRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SshKeyInfo"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"summary":"List Ssh Keys","description":"List the caller's own SSH keys, newest first.","operationId":"list_ssh_keys_ssh_keys_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":{"$ref":"#/components/schemas/SshKeyListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/ssh-keys/{key_id}":{"delete":{"summary":"Delete Ssh Key","description":"Delete one SSH key the caller owns.\n\nA key of another account answers 404 and not 403, the\n``DELETE /tokens/{id}`` rule: the answer tells a stranger nothing\nabout the existence of a row.","operationId":"delete_ssh_key_ssh_keys__key_id__delete","parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Key Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/deploys":{"get":{"summary":"Project Deploy History","description":"One bounded read across a project's channels, without browser fan-out.","operationId":"project_deploy_history_apps__app_id__deploys_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":50,"minimum":1,"default":30,"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":"array","items":{"$ref":"#/components/schemas/ProjectDeployHistoryItem"},"title":"Response Project Deploy History Apps  App Id  Deploys Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/channels/{channel_id}/deploys":{"get":{"summary":"Channel Deploy History","description":"Bounded channel-scoped history; no log contents or fleet projection.","operationId":"channel_deploy_history_apps__app_id__channels__channel_id__deploys_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Channel Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":50,"minimum":1,"default":30,"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":"array","items":{"$ref":"#/components/schemas/DeployHistoryItem"},"title":"Response Channel Deploy History Apps  App Id  Channels  Channel Id  Deploys Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps":{"post":{"summary":"Create App","operationId":"create_app_apps_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/CreateAppRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"summary":"List Apps","description":"List every app the caller owns or is an active member of.\n\nThe route costs THREE statements, and one when the caller has no\napp. The first joins ``apps`` to its owner and outer-joins the\ncaller's membership row. It therefore gives the app, the role and\nthe owner row at once. ``protected.resolve`` also reads the owner\nrow. The second reads the channels of every app in the list, and the\nthird reads the pending deploy of every channel in one grouped\nstatement. The route only reads, so it runs on\n``run_read_in_thread``.","operationId":"list_apps_apps_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":{"$ref":"#/components/schemas/AppListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}":{"get":{"summary":"Get App","description":"Report one app the caller owns or is an active member of.\n\nThe route only reads, so it runs on ``run_read_in_thread``.\n``load_app_for`` reads the app and the caller's membership row in\none statement. One more statement reads the owner, one more reads\nthe channels, and one more reads the pending deploys of those\nchannels. ``protected.resolve`` reads the owner from the identity\nmap and adds no statement.","operationId":"get_app_apps__app_id__get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Delete App","operationId":"delete_app_apps__app_id__delete","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/postgres/external":{"post":{"summary":"Set App Postgres External","description":"Toggle the app-wide external Postgres access opt-in.\n\nDefault-closed and per project. When enabled, every channel of the\napp becomes reachable over the public TLS endpoint; disabling it\ncloses all of them immediately. Cross-user callers get 404.","operationId":"set_app_postgres_external_apps__app_id__postgres_external_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App 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/ExternalDbAccessRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/blob/external":{"post":{"summary":"Set App Blob External","description":"Toggle the app-wide external object-store access opt-in.\n\nDefault-closed and per project. When enabled, every channel of the\napp becomes reachable over the public S3 gateway endpoint; disabling\nit closes all of them immediately. Cross-user callers get 404.\n\nThe flag is enforced on the owning CELL, from the pushed blob record,\nso the toggle pushes the app's records straight after the commit.\nA push failure is a 5xx: on enable that leaves external access closed\n(safe); on disable the DB says off while the cell may still admit\nuntil the sweeper's re-push heals it — hence the ERROR log there.","operationId":"set_app_blob_external_apps__app_id__blob_external_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App 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/ExternalBlobAccessRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/forwarding":{"post":{"summary":"Set App Port Forwarding","description":"Toggle the app-wide public raw-TCP endpoint opt-in.\n\nDefault-closed and per project. While it is off, no channel of the app\ncan be given a public endpoint and any endpoint already allocated stops\nresolving — turning it off is the immediate kill switch. Cross-user\ncallers get 404.\n\nRequires :attr:`Role.ADMIN`, unlike the external Postgres and\nobject-store toggles: it sits ABOVE the per-channel expose/unexpose\nroutes it gates, so a member cannot re-open every endpoint an admin\ndeliberately closed.","operationId":"set_app_port_forwarding_apps__app_id__forwarding_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App 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/PortForwardingRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/channels":{"post":{"summary":"Create Channel","operationId":"create_channel_apps__app_id__channels_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App 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/CreateChannelRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChannelResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"summary":"List Channels","description":"List the channels of one app the caller can reach.\n\nThe route only reads, so it runs on ``run_read_in_thread``. It costs\nthree round trips to the control DB: the access check, the channels,\nand the pending deploy of every channel in one grouped statement.","operationId":"list_channels_apps__app_id__channels_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App 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":"array","items":{"$ref":"#/components/schemas/ChannelResponse"},"title":"Response List Channels Apps  App Id  Channels Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/channels/{channel_id}":{"get":{"summary":"Get Channel","description":"Report one channel of an app the caller can reach.\n\nThe route only reads, so it runs on ``run_read_in_thread``.\n``load_channel_for`` reads the app, the membership row and the\nchannel in one statement; one more reads the pending deploy.","operationId":"get_channel_apps__app_id__channels__channel_id__get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Channel Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChannelResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"summary":"Update Channel","description":"Change a channel's branch binding, whether it follows that branch, or both.\n\nThe scope and role are ``create_channel``'s: ``channel:*`` and a\nMEMBER. The branch does not have to exist in the repo yet.\n``channels/service.py::set_binding`` validates the binding and\njournals the change.\n\nTurning ``follows_branch`` on also takes ``deploy:*``, because from\nthen on each GitHub push deploys the channel. It needs a GitHub\nconnection and a binding (``channels/service.py::set_follows_branch``).\nTurning it off is always accepted. A body that carries both fields\napplies the binding first, so one request can bind a branch and\nfollow it.","operationId":"update_channel_apps__app_id__channels__channel_id__patch","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Channel 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/UpdateChannelRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChannelResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Delete Channel","operationId":"delete_channel_apps__app_id__channels__channel_id__delete","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Channel Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/channels/{channel_id}/deploy":{"post":{"summary":"Enqueue Channel Deploy","operationId":"enqueue_channel_deploy_apps__app_id__channels__channel_id__deploy_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Channel 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/DeployRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeployResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/channels/{channel_id}/rewind":{"post":{"summary":"Enqueue Channel Rewind","description":"Instant one-step rewind to the channel's previous deploy image.\n\nBoots the immediately-previous retained image directly (no build,\nno git sync, no snapshot) via the deploy dispatcher, so it can't race a\nconcurrent deploy/move on the same channel. \"Previous\" is the newest\n``success`` deploy whose SHA differs from the channel's current one.\nRefused for ``static`` apps (no built image) and when the channel\nhas only ever deployed one SHA.","operationId":"enqueue_channel_rewind_apps__app_id__channels__channel_id__rewind_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Channel Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeployResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/channels/{channel_id}/logs":{"get":{"summary":"Get Deploy Log","description":"Return one deploy's status plus a byte window of its stored log.\n\nThe log bytes come from the deploy's ``deploy_log_chunks`` rows —\ncontiguous, line-aligned slices with stored ``start_offset`` /\n``byte_len`` byte accounting, so every window figure is byte math\nover stored lengths, never ``len(str)`` over decoded text.\n\nWith no ``offset`` the window is the LAST ``max_bytes`` bytes of the\nlog, advanced past the first newline inside the window so the log\nstarts on a whole line. The snap runs only when at least one byte\nfollows that newline; when the newline ends the window, the raw\nwindow is returned unsnapped so the reply is never emptied. An\nexplicit ``offset`` reads byte-exactly from that offset with no line\nsnapping; an offset past the end of the log clamps to the log size\nand yields an empty log. ``window_bytes`` is the byte length of the\nreturned window before utf-8 decoding: the next page starts at\n``offset + window_bytes``, and ``offset + window_bytes ==\nlog_bytes`` means the reply reaches the end of the log. A window\nboundary can still split a multibyte character (``errors=\"replace\"``\ncovers the decode), exactly as the file implementation did. A deploy\nwith no chunks yet (a queued deploy) answers 200 with an empty log\nand ``log_bytes=0`` — 404 stays for a missing or foreign deploy row\nonly. A ``superseded`` deploy answers 200 too, with\n``superseded_by`` naming its successor: its log is empty when a newer\nrequest replaced it in the queue, or partial plus one marker line\nwhen a planned stop superseded it mid-run. A reader paging a deploy\nwhose chunks the sweeper prunes\nmid-pagination sees the remaining pages empty; the race is accepted,\nnot defended — the log was already past retention.\n\nThe route only reads, so it runs on ``run_read_in_thread``, and the\nwhole answer costs one round trip: the access check, the deploy row,\nthe log-size lookup, and one indexed range query over the chunks\noverlapping the window all run inside the same ``_load`` DB hop.","operationId":"get_deploy_log_apps__app_id__channels__channel_id__logs_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Channel Id"}},{"name":"deploy","in":"query","required":true,"schema":{"type":"string","format":"uuid","title":"Deploy"}},{"name":"offset","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"title":"Offset"}},{"name":"max_bytes","in":"query","required":false,"schema":{"type":"integer","maximum":262144,"minimum":1,"default":16384,"title":"Max Bytes"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeployLogResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/channels/{channel_id}/deploys/{deploy_id}/env":{"get":{"summary":"Get Deploy Env","description":"Return the env snapshot recorded when a deploy started.\n\nUser vars carry their plaintext value for ``kind='env'`` and a\n``None`` value for ``kind='secret'``; system-injected keys are\nlisted by name only. 404 when the deploy does not exist under this\napp/channel or has no snapshot (deploys predating the snapshot\nfeature).","operationId":"get_deploy_env_apps__app_id__channels__channel_id__deploys__deploy_id__env_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Channel Id"}},{"name":"deploy_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Deploy Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeployEnvSnapshotResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/channels/{channel_id}/runtime/log":{"post":{"summary":"Get Runtime Log","description":"Read a channel container's stdout/stderr, live or archived.\n\nThe runtime counterpart of ``get_deploy_log``, which only covers the\nbuild/boot window: this reads a RUNNING app's ongoing output, and —\nonce the container is gone (a blue-green redeploy removes it) — the\narchive the cell wrote at removal, so a crash is still diagnosable\nafterwards (RUNTIME.md). The status facts (status, exit code, OOM\nflag, restart count) ride on every reply, so omitting ``command`` is\nthe \"just tell me how it died\" query.\n\n**POST, not GET**, because ``command`` is a shell pipeline: URL-\nencoding one is hostile, and a query string lands in Caddy's access\nlog (and thus xhost_stats), where a ``grep <secret>`` must never go.\n\nAuth mirrors ``get_deploy_log`` deliberately: bearer + channel\nownership + the ``deploy:*`` scope both deploy-plane reads carry.\nThe command grants the caller no authority they lack — a read-only\ncopy of their own log inside a networkless disposable container —\nso resource abuse is answered by the cell's caps, not by a\npermission bit, and an owner-only gate would break sharing\nasymmetrically against ``get_deploy_log``.","operationId":"get_runtime_log_apps__app_id__channels__channel_id__runtime_log_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Channel 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/RuntimeLogRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RuntimeLogResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/channels/{channel_id}/images":{"get":{"summary":"Get Channel Images","description":"Live built-image inventory for one channel, newest first.\n\nThe tenant-scoped sibling of the admin host-detail images payload\n(`routes/admin_hosts.py::_host_images`): one read-only RPC per page\nload — no caching, no retries — filtered on the agent by the\nchannel's full-UUID labels. ``images`` degrades to ``null`` (never\nan error) when the host has no registered ``RemoteHostAgent`` or\nthe agent is unreachable, so the console settings page always\nrenders. ``build_queue`` is deliberately NOT exposed — it is\nhost-global and would leak other tenants' activity (admin-only).\nEach image's ``charged_size_bytes`` and ``matched_base`` come from\nthe build record in the control DB\n(:func:`~xhostd.deploy.pipeline.charged_sizes`), joined on the\nagent row's ``image_id``; an image with no record carries ``null``.","operationId":"get_channel_images_apps__app_id__channels__channel_id__images_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Channel Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChannelImagesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/env":{"post":{"summary":"Upsert Env","operationId":"upsert_env_apps__app_id__env_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App 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/UpsertEnvRequest"}}}},"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"summary":"List Env","operationId":"list_env_apps__app_id__env_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"channel_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Channel Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnvListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/env/{key}":{"put":{"summary":"Put Env Value","description":"Store one env value from the raw request body.\n\nThe upload path for a secret: the value travels from the caller's\nfile straight here, never through an MCP tool call, and\n``set_secret`` describes this request (design/CONTROL-PLANE.md, the\nenv surface). ``kind`` defaults to ``secret`` and never keeps an\nexisting kind.","operationId":"put_env_value_apps__app_id__env__key__put","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}},{"name":"kind","in":"query","required":false,"schema":{"$ref":"#/components/schemas/EnvKind","default":"secret"}},{"name":"channel_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Channel Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Delete Env","operationId":"delete_env_apps__app_id__env__key__delete","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}},{"name":"channel_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Channel Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/env/{key}/generate":{"post":{"summary":"Generate Env Secret","description":"Store a new random secret under a new key, and return nothing.\n\nThe value is ``secrets.token_urlsafe(32)``, 256 bits, the\n``routes/passwords.py`` precedent. Create-only: a key that already\nhas a row in that scope answers 409, so a generate never replaces a\nvalue the app depends on. The response carries no value; the\nconsole's audited reveal is the read path.","operationId":"generate_env_secret_apps__app_id__env__key__generate_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}},{"name":"channel_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Channel Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/env/{key}/value":{"get":{"summary":"Get Env Value","description":"Reveal one env value — the only read path for ``kind='secret'``.\n\n``channel_id`` given: that channel's row, falling back to the\napp-level row when the channel has no override (resolved\nsemantics). No ``channel_id``: the app-level row only. Every call\nrecords an ``env.reveal`` journal event before the value is\nreturned (blob-credentials precedent, ``routes/blob.py``).\n\nA protected action: the gate runs at the top of the worker, before\n``crypto.decrypt``, so the route never decrypts a secret it then\nrefuses.","operationId":"get_env_value_apps__app_id__env__key__value_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}},{"name":"channel_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Channel Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnvValueResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/tree":{"get":{"summary":"Get App Tree","description":"List the files at ``ref``; an omitted ref is ``omitted_ref_branch``'s.","operationId":"get_app_tree_apps__app_id__tree_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"ref","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ref"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TreeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/blob":{"get":{"summary":"Get App Blob","description":"Read one file at ``ref``; an omitted ref is ``omitted_ref_branch``'s.","operationId":"get_app_blob_apps__app_id__blob_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"path","in":"query","required":true,"schema":{"type":"string","title":"Path"}},{"name":"ref","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ref"}},{"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"}}}}}}},"/apps/{app_id}/channels/{channel_id}/code/download":{"get":{"summary":"Download Channel Code","description":"Stream a ``tar`` of one checkpoint's code.\n\n``snapshot_id`` names a Postgres snapshot of this channel, and the\nroute streams the sha that row recorded. Without it the route streams\nthe channel's ``current_sha``, which is what an export holds today.\nThe parameter is deliberately not an arbitrary ``ref``: this route\nserves checkpoints, and ``GET /apps/{app_id}/tree`` already serves an\narbitrary ref.\n\nThe channel load runs FIRST, so a stranger reads 404. The flag\nfollows it. A row with no recorded sha answers 404\n``snapshot_sha_unknown``, and so does a channel that never deployed.\nA sha the cell cannot resolve answers 404 ``snapshot_code_gone``: the\ncell's pin ref protects the current sha alone, so a force-push plus a\nlater ``git gc`` can remove an old checkpoint's commit. Both 404s are\npermanent, and a client must not repeat the call.","operationId":"download_channel_code_apps__app_id__channels__channel_id__code_download_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Channel Id"}},{"name":"snapshot_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Snapshot 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"}}}}}}},"/apps/{app_id}/changeset":{"post":{"summary":"Post App Changeset","operationId":"post_app_changeset_apps__app_id__changeset_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App 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/ChangesetRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangesetResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/user/stats":{"get":{"summary":"Api My Stats","description":"Return stats for the authenticated user, MEMBER-scoped.\n\nThe counts and the site rows cover every project the caller can see,\nthe ``GET /me/stats`` scope, so a project shared with the caller\ncounts. The ``resources`` block stays owner-scoped (STATS.md).","operationId":"api_my_stats_api_user_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","additionalProperties":true,"title":"Response Api My Stats Api User Stats Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/webhooks/paddle":{"post":{"summary":"Paddle Webhook","operationId":"paddle_webhook_webhooks_paddle_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/feedback":{"post":{"summary":"Submit Feedback","operationId":"submit_feedback_feedback_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/SubmitFeedbackRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmitFeedbackResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"summary":"List Feedback","description":"List the caller's own reports with the team's answers.\n\nScoped to the caller's account, the way ``GET /exports`` is. The\naccount is one account, so a page holds a console report too.\n\n``cursor`` walks to the older reports. The payload answers\n``next_cursor``, which is the value of the next call, or None at the\nend of the account's reports.\n\nThe route takes NO ``report`` parameter. That parameter serves the\nconsole bell anchor alone, and a public parameter is a published\nsurface the platform must then keep (FEEDBACK.md).","operationId":"list_feedback_feedback_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}},{"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":true,"title":"Response List Feedback Feedback Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/threads":{"post":{"summary":"Create Thread","description":"Open one thread on the app, with its first note.\n\nA given ``channel_id`` must name a channel of this app;\n:func:`~xhostd.access.load_channel_for` answers 404 for any other\nchannel. The route locks the app row and takes the next per-app\nthread number; ``uq_note_threads_app_number`` is the backstop. The\nroute copies the token label into ``author_label`` at write time\n(NOTES.md), because a token label can recycle later. The write\njournals ONE ``note.thread`` event; the first note writes no\n``note.add`` row.","operationId":"create_thread_apps__app_id__threads_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App 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/CreateThreadRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateThreadResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"summary":"List Threads","description":"List the threads of the app, last activity first.\n\nThe order is the last note time descending, with the thread's own\n``created_at`` for a thread with no notes and the number as the\ntie break. The filters are exact matches; ``query`` is a\ncase-insensitive contains match on the subject. Each row carries\n``note_count`` and ``last_note_at``.","operationId":"list_threads_apps__app_id__threads_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"query","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Query"}},{"name":"category","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"}},{"name":"channel_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Channel Id"}},{"name":"branch","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Branch"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"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":{"$ref":"#/components/schemas/ThreadListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/threads/{number}":{"get":{"summary":"Get Thread","description":"Read one thread of the app, with its notes oldest first.\n\nThe order is ``created_at`` ascending with the row id as the tie\nbreak, so the discussion reads top to bottom. Each note carries the\nvote counts and the member denominator. An unknown number reads\n404.","operationId":"get_thread_apps__app_id__threads__number__get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"number","in":"path","required":true,"schema":{"type":"integer","title":"Number"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ThreadDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/threads/{number}/notes":{"post":{"summary":"Add Thread Note","description":"Write one note on an existing thread of the app.\n\nAn unknown number reads 404. The route copies the token label into\n``author_label`` at write time (NOTES.md). The write journals one\n``note.add`` event with the thread number as the target.","operationId":"add_thread_note_apps__app_id__threads__number__notes_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"number","in":"path","required":true,"schema":{"type":"integer","title":"Number"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddThreadNoteRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NoteResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/notes":{"get":{"summary":"Search Notes","description":"Search the internal notes of the app, newest first.\n\nThe order is ``created_at`` descending with the row id as the tie\nbreak. ``query`` is a case-insensitive contains match on the body;\n``category`` is an exact match. Each hit carries the vote counts,\nthe member denominator, and its thread's number and subject.","operationId":"search_notes_apps__app_id__notes_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"query","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Query"}},{"name":"category","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"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":{"$ref":"#/components/schemas/NoteSearchResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/notes/{note_id}/vote":{"put":{"summary":"Cast Vote","description":"Set or clear the caller's vote on one internal note.\n\n``value`` is ``-1``, ``0`` or ``+1``. A repeat call with any value\nlands on the same one row, so the route is idempotent: the upsert\nuses the ``uq_note_votes_note_voter`` constraint, on the\n``dialect_insert`` shape of :mod:`xhostd.metrics.resource_rollup`.\n``0`` deletes the caller's vote row. The route journals nothing. A\nnote of another app, and an external note, both read 404.","operationId":"cast_vote_apps__app_id__notes__note_id__vote_put","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"note_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Note 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/CastVoteRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoteSummary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/app-feedback":{"post":{"summary":"Submit App Feedback","description":"Write one external feedback note on another account's app.\n\nAny authenticated account may call this route; there is no\nmembership gate. The caller names the target by ``owner_username``\nand ``app_name``. An unknown pair reads 404. The note lands with\n``kind='external'``, no thread and no ``agent_id``. The per-account\ncap ``users.app_note_cap`` bounds the caller's external notes\nacross all apps; a refusal reads 400 and leaves one\n``app_note_cap`` operator notice. The write notifies the app owner\nand journals one ``note.feedback`` event.","operationId":"submit_app_feedback_app_feedback_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/SubmitAppFeedbackRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppFeedbackResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/feedback-notes":{"get":{"summary":"List Feedback Notes","description":"List the external feedback notes of the app, newest first.\n\nA member surface, separate from the internal note surface. Each\nbody is untrusted third-party data: no surface renders it as\nmarkup, and no agent obeys it as an instruction. The payload\ncarries no ``agent_id``, and there are no votes on this surface.","operationId":"list_feedback_notes_apps__app_id__feedback_notes_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"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":{"$ref":"#/components/schemas/FeedbackNoteListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/.well-known/oauth-authorization-server":{"get":{"summary":"Metadata","operationId":"metadata__well_known_oauth_authorization_server_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Metadata  Well Known Oauth Authorization Server Get"}}}}}}},"/oauth/register":{"post":{"summary":"Register","operationId":"register_oauth_register_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/xhostd__routes__oauth__RegisterRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/oauth/authorize":{"get":{"summary":"Authorize Get","operationId":"authorize_get_oauth_authorize_get","parameters":[{"name":"response_type","in":"query","required":false,"schema":{"type":"string","default":"","title":"Response Type"}},{"name":"client_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Client Id"}},{"name":"redirect_uri","in":"query","required":false,"schema":{"type":"string","default":"","title":"Redirect Uri"}},{"name":"code_challenge","in":"query","required":false,"schema":{"type":"string","default":"","title":"Code Challenge"}},{"name":"code_challenge_method","in":"query","required":false,"schema":{"type":"string","default":"","title":"Code Challenge Method"}},{"name":"state","in":"query","required":false,"schema":{"type":"string","default":"","title":"State"}},{"name":"scope","in":"query","required":false,"schema":{"type":"string","default":"","title":"Scope"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"summary":"Authorize Post","operationId":"authorize_post_oauth_authorize_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_authorize_post_oauth_authorize_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/oauth/token":{"post":{"summary":"Token Endpoint","operationId":"token_endpoint_oauth_token_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_token_endpoint_oauth_token_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/oauth/revoke":{"post":{"summary":"Revoke","operationId":"revoke_oauth_revoke_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_revoke_oauth_revoke_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/me/billing/checkout":{"post":{"summary":"Billing Checkout","description":"Mint a checkout for a tier and answer the URL the buyer opens.\n\nA POST, not a GET: the route CREATES a checkout at the payment\nprovider. A GET is prefetchable, so a link scanner would mint a real\ncheckout each time a user points at the link. Every route that mints\noutbound is a POST\n(``POST /me/billing/plan``, ``POST /apps/{id}/exports``).\n\nConsole-only, per-current-user: Bearer-authed like the ``/exports``\nroutes — the web server forwards the session token in the\n``Authorization: Bearer`` header and ``current_token`` verifies it.\nThe route then demands ``billing:write``, which the console session\nmint alone grants, so an agent credential answers 403. The caller's\nid goes to the provider as the buyer hint, so the provider's webhook\ncan bind the new subscription back to this account.\n\nThe route refuses a caller who already holds a granting subscription\n(409). A provider accepts a second subscription and charges the card\ntwice, and the plan projection takes the highest granting tier, so\nthe extra subscription stays invisible. xhostd owns subscription\nstate, so the guard belongs here and not in the console.\n\n**The 409 is the one enforcement point on the PUBLIC surface, and it\nis no longer the only mint point.**\n``python -m xhostd.cli.admin mint-checkout``\n(:mod:`xhostd.cli.admin`) mints the same checkout and skips exactly\nthis guard, so an operator can hand a live subscriber a second\ncheckout without a gap in their plan. That verb needs a shell on the\nedge box, so nothing a caller sends reaches it.\n\nThe route hands the provider no landing URL. The client took\n``settings.platform_domain`` at construction\n(``xhostd/__main__.py::_build_billing_client``) and builds the apex\ncheckout page from :mod:`xhostd.billing` itself, so a target from\nthe console cannot become an open-redirect input.","operationId":"billing_checkout_me_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/CheckoutRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/me/billing/portal":{"get":{"summary":"Billing Portal","description":"Return the signed-in user's live subscription summary, or nulls.\n\nThe summary is the user's live (granting) subscription:\n``{portal_available, status, tier, renews_at, ends_at,\ngranting_count}``, drawn from the newest granting row. With no live\nsubscription every field is ``null`` or ``false`` and the count is\n``0`` — the console then hides the \"Invoices and payment method\"\nbutton. Used by the account page to render current plan status +\nrenews/ends and that button. Bearer-authed like the ``/exports``\nroutes — the web server forwards the session token in the\n``Authorization: Bearer`` header and ``current_token`` verifies it.\n\n**The route reports a BOOLEAN, never a URL.** Paddle's portal link is\nsingle-use and short-lived, so a render-time mint would burn one\nsession per account page and hand a second reader a spent link. The\nconsole renders a button from this flag and mints at the click\nthrough ``POST /me/billing/portal``. The flag restates that route's\nown precondition, so a ``true`` here means the POST can mint, and it\nfails closed: an unconfigured box reports ``false``.\n\nThe flag carries no scope gate. The capability is the mint, and the\nPOST demands ``billing:write`` for it (TEN-03).\n\n``granting_count`` is how many subscriptions of the caller grant a\ntier at that one clock. The console plan page reads it to pick one of\nits three states, because ``POST /me/billing/plan`` refuses a count\nother than one. The count needs no second query: the loop below\nalready reads every row.","operationId":"billing_portal_me_billing_portal_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"}}}}}},"post":{"summary":"Billing Portal Session","description":"Answer the URL that opens the caller's billing management.\n\nA POST, not a GET, for the reason ``POST /me/billing/checkout``\nstates: the route MINTS a single-use portal session, and a GET is\nprefetchable, so a link scanner would burn the session before the\nperson clicks. The route demands ``billing:write``, which\nthe console session mint alone grants: the portal opens the invoices,\nthe payment method and a cancel, so an agent credential answers 403\n(TEN-03).\n\nThe route mints when the box holds a Paddle client AND the caller's\ngranting row names a Paddle customer. It answers 503 when either is\nmissing: an unconfigured box has nothing to mint against, and a row\nwith an empty customer id came from a payload that carried none,\nwhich is an operator's problem rather than the user's retry\n(design/OPS-RUNBOOK.md).\n\nA caller with no granting subscription answers 409: there is no\nsubscription to manage, which is the checkout route's shape for a\nstate the console must name rather than retry.\n\nThe route caches nothing. A Paddle portal session is single-use, so a\ncached link would hand the second clicker a spent URL.","operationId":"billing_portal_session_me_billing_portal_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"}}}}}}},"/me/billing/plan":{"post":{"summary":"Billing Plan","description":"Change the signed-in user's plan at the payment provider.\n\nONE route carries both directions, because the SERVER decides what a\ntarget tier means. A move to ``basic`` is a cancel, because ``basic``\nis the free tier and no provider sells it. A move to another paid\ntier is a price change. The route refuses an agent-registration-only\ntier with 400, because no subscription grants one.\n\nBearer-authed like the billing routes above: the route reads\n``caller.user_id`` and acts on that user alone. It also demands\n``billing:write``, which the console session mint alone grants, so an\nagent credential answers 403. It is on the PUBLIC app on purpose. The\ninternal listener authenticates the console process, not the person,\nso a route there would let a console fault move another user's money.\n\nThe route acts on the caller's ONE granting subscription. It refuses\nwhen the caller has none, and it refuses when the caller has two or\nmore: a silent pick would change one subscription and leave the other\nto charge. For a subscription that is not cancelled, a target that\nequals the subscription's current tier calls the provider not at all,\nwhich stops a double proration on a double submit.\n\nA cancelled subscription still grants its tier until ``ends_at``, so\nit is the caller's one live subscription. The provider refuses a\nprice change on it while the scheduled cancel exists, so the route\nreads the target differently:\n\n- The current tier keeps the plan: the route removes the scheduled\n  cancel, and the subscription renews on ``ends_at``.\n- ``basic`` calls the provider not at all, because the subscription\n  already ends at ``ends_at``.\n- Another paid tier answers 409 and calls the provider not at all.\n  The user keeps the plan first, and then changes it.\n\nThe answer's ``action`` is one of ``unchanged`` (no provider call),\n``cancelled``, ``changed``, or ``kept``. Each request makes at most one\nprovider call.\n\nThe route passes the direction it already ranked, and each provider\nturns that into its own proration flags: an upgrade charges the\ndifference now, so the user pays for the bigger machine at once, and\na downgrade credits it onto the next invoice. A downgrade must never\nproduce a charge.\n\nThe route writes NO database field. ``users.plan`` stays\nprojection-only, and the provider's webhook lands seconds later with\nthe new state. The route also does not wait for an active\n``plan_apply`` job: the provider owns the money, and a refusal of a\npaid upgrade is worse than a late apply.","operationId":"billing_plan_me_billing_plan_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/PlanChangeRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/plans":{"get":{"summary":"List Plans","description":"Return every plan tier and its caps, lowest rank first.\n\nEach row carries ``tier``, ``rank`` and the caps a user shops on. It\ncarries no price — money lives at the payment provider (BILLING.md).","operationId":"list_plans_plans_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/registrations":{"post":{"summary":"Register","description":"Create a ``starter`` account for the holder of an Ed25519 key.\n\nThe checks run in a fixed order: key parse, timestamp window,\nsignature, username, label, and then one transaction that reads the\npolicy row, counts the two budgets, and inserts the user, the key,\nthe token, and the notification. A username conflict on a random\nname retries with a new name up to :data:`ALLOCATION_ATTEMPTS`\ntimes. A budget refusal answers 429 and writes one platform event;\na closed policy answers 503 and writes none.","operationId":"register_registrations_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/xhostd__routes__registration__RegisterRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegistrationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/ssh-key":{"post":{"summary":"Ssh Key Login","description":"Mint a 30-day token for an ``api_login`` key.\n\nThe route finds the key row by the fingerprint of the presented\nline, verifies the signature against the STORED blob, and mints\nunder the ``ssh-key-login`` label with the 20-token cap. An unknown\nfingerprint and a key without ``api_login`` answer the same 404.","operationId":"ssh_key_login_auth_ssh_key_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SshKeyLoginRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SshKeyLoginResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/me/email-verifications":{"post":{"summary":"Request Verification","description":"Mail one confirmation link for the caller's account.\n\nThe checks run in a fixed order: ``email:bind`` (403), the address\nshape (400), then under the ``users`` row lock the account's\nverified email (409), the account's 60-second window (429), and\nthen, under the address lock, the address's two caps (429). A new\nlink deletes the account's unconsumed ``email_bind`` rows, so one\nlink at a time works. Every refusal precedes the write, so the\nclosure can raise.\n\nThe scope gate sits here and not on the confirmation, because this\nis the route that chooses the address. A bound address is what a\nGoogle sign-in matches on, so it opens a console session carrying\n``billing:write`` and ``console:session``. The scope keeps that out\nof reach of every bearer that did not come from a key:\n``POST /ssh-keys`` refuses to register the key that mints it for an\naddress-less account unless the caller holds the scope already, the\nboundary design/AUTH.md states.","operationId":"request_verification_me_email_verifications_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/VerificationRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerificationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"summary":"Verification Status","description":"Answer where the account's verification stands.\n\nThe route takes a bearer and no scope: it answers a state and the\nplan, and never the address or the link. The person who confirms\ndoes it in a browser, so this read is how the agent learns the\noutcome.","operationId":"verification_status_me_email_verifications_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":{"$ref":"#/components/schemas/VerificationStatus"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/me/email-verifications/complete":{"post":{"summary":"Retired Complete","description":"The retired code route. It reads no body and no account.\n\nAn older client still calls it with a code. The answer names the\ntwo routes that replace it, and the log line lets an operator count\nthe callers before the path goes.","operationId":"retired_complete_me_email_verifications_complete_post","responses":{"410":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/me/usage/resources":{"get":{"summary":"My Resource Usage","description":"Per-account CPU/memory history against the plan's soft allocation.\n\n``cpu_pct_of_limit`` is the bucket's average cores as a percentage of\nthe plan's soft CPU allocation (100% == the soft entitlement; may\nexceed to the ``plans.CPU_BURST`` × 100% burst cap). ``current`` comes\nfrom the in-memory latest-sample cache, falling back to the newest\nbucket if the cache is cold.\n\nThe route only reads, so it runs on ``run_read_in_thread``. One\nstatement reads the user, and ``_resource_rows`` adds one more.","operationId":"my_resource_usage_me_usage_resources_get","parameters":[{"name":"range","in":"query","required":false,"schema":{"type":"string","default":"24h","title":"Range"}},{"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":true,"title":"Response My Resource Usage Me Usage Resources Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/me/usage/bandwidth":{"get":{"summary":"My Bandwidth Usage","description":"Daily egress of the caller's OWN projects, one point per day.\n\nThe series reads ``access_summary_daily`` and is OWNER-scoped, like\nthe monthly figure of ``GET /me/usage``. A shared project therefore\ncounts for its owner alone, and the two numbers agree.\n\n``points`` holds one point per day of the range, oldest first. ``t``\nis the ISO-Z timestamp at the start of the day and ``bytes_out`` is\nthat day's egress. A day with no row carries a zero. The newest point\nis YESTERDAY, because the table finalizes a day at 00:05 UTC.\n\nThe payload carries NO plan cap, because egress has none. The\nplatform measures egress and publishes no allowance beside it.","operationId":"my_bandwidth_usage_me_usage_bandwidth_get","parameters":[{"name":"range","in":"query","required":false,"schema":{"type":"string","default":"30d","title":"Range"}},{"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":true,"title":"Response My Bandwidth Usage Me Usage Bandwidth Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/me/stats":{"get":{"summary":"My Stats","description":"Account-wide access stats, plus one row per project.\n\nThe payload is the ordinary stats shape over every channel the caller\ncan see, plus a ``projects`` list. The channel set is MEMBER-scoped\n(``_visible_channels``), so a project shared with the caller counts\nhere. That is a different scope from ``GET /me/usage``, which is\nowner-scoped, so the two surfaces answer legitimately different\nnumbers.\n\nAn account with no channel gets the empty stats shape and an empty\n``projects`` list.\n\nThe route only reads, so it runs on ``run_read_in_thread``.","operationId":"my_stats_me_stats_get","parameters":[{"name":"range","in":"query","required":false,"schema":{"type":"string","default":"24h","title":"Range"}},{"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":true,"title":"Response My Stats Me Stats Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/me/apps/{app_id}/channels/{channel_id}/stats":{"get":{"summary":"Channel Stats","description":"Report the access stats of one channel the caller can reach.\n\nThe route makes two dispatches, and both only read, so both run on\n``run_read_in_thread``. The first checks the access in one\nstatement. The second builds the payload.","operationId":"channel_stats_me_apps__app_id__channels__channel_id__stats_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Channel Id"}},{"name":"range","in":"query","required":false,"schema":{"type":"string","default":"24h","title":"Range"}},{"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":true,"title":"Response Channel Stats Me Apps  App Id  Channels  Channel Id  Stats Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/me/apps/{app_id}/channels/{channel_id}/health":{"get":{"summary":"Channel Health","description":"Report why one channel is slow or unhealthy, plus what to do.\n\nThe reply carries five blocks and a findings list. The resource\nblock covers the OWNER's whole account, so a member reads it as not\navailable. The runtime block reads the container status facts off\nthe host. The build block counts the recent builds the kernel\nstopped. The database block reads the channel's own cell Postgres.\nThe latency block reads the request-duration histogram of the\nchannel over the last day, and it covers the server-side span\nalone.\n\nA source the route cannot read reports ``available=false`` with a\nreason, and the route still answers 200. The findings list is never\nempty: a channel with no fault carries the ``healthy`` finding.\n\nThe control-DB read only reads, so it runs on\n``run_read_in_thread``. The host read and the database read follow\nit and each degrade on their own.","operationId":"channel_health_me_apps__app_id__channels__channel_id__health_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Channel Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChannelHealth"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/me/apps/{app_id}/stats":{"get":{"summary":"App Stats","description":"Report the access stats of every channel of one app.\n\nThe route makes two dispatches, and both only read, so both run on\n``run_read_in_thread``. The first checks the access and reads the\nchannel ids in two statements. The second builds the payload.","operationId":"app_stats_me_apps__app_id__stats_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"range","in":"query","required":false,"schema":{"type":"string","default":"24h","title":"Range"}},{"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":true,"title":"Response App Stats Me Apps  App Id  Stats Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/stats":{"get":{"summary":"Admin Stats","description":"Report the access stats of every channel of the fleet.\n\nThe query only reads, so it runs on ``run_read_in_thread``.\n``require_admin`` keeps its own dispatch on ``run_in_thread``, and\nthat dispatch adds one statement before the query.","operationId":"admin_stats_admin_stats_get","parameters":[{"name":"range","in":"query","required":false,"schema":{"type":"string","default":"24h","title":"Range"}},{"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":true,"title":"Response Admin Stats Admin Stats Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/stats/unrouted":{"get":{"summary":"Admin Unrouted","description":"Report the top unrouted hostnames and paths of the fleet.\n\nThe query only reads, so it runs on ``run_read_in_thread``. It costs\none round trip. ``require_admin`` keeps its own dispatch on\n``run_in_thread``, and that dispatch adds one statement before the\nquery.","operationId":"admin_unrouted_admin_stats_unrouted_get","parameters":[{"name":"range","in":"query","required":false,"schema":{"type":"string","default":"24h","title":"Range"}},{"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":true,"title":"Response Admin Unrouted Admin Stats Unrouted Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/channels/{channel_id}/postgres":{"get":{"summary":"Get Channel Postgres","description":"Report the Postgres state of one channel the caller can reach.\n\nThe route only reads, so it runs on ``run_read_in_thread``. An owner\ncosts four round trips to the control DB: the access check, the\n``channel_schemas`` row with the profile of the instance that holds\nit, which sets the displayed limit, and the two reads of the\nchannel's restore (``postgres/api.py::channel_restore``), a third\nwhen the channel has one, and three more for its queue state while\nthe restore is ``received`` (:func:`_restore_response`). A member\nreads the owner name too, for the\nexternal database name. The connection count and the size come from\nthe tenant Postgres, not from the control DB.","operationId":"get_channel_postgres_apps__app_id__channels__channel_id__postgres_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Channel Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChannelPostgresStatus"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/channels/{channel_id}/postgres/reset":{"post":{"summary":"Reset Channel Postgres","operationId":"reset_channel_postgres_apps__app_id__channels__channel_id__postgres_reset_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Channel 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/ResetRequest"}}}},"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/channels/{channel_id}/postgres/dump":{"get":{"summary":"Dump Channel Postgres","operationId":"dump_channel_postgres_apps__app_id__channels__channel_id__postgres_dump_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Channel 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"}}}}}}},"/me/postgres/storage":{"get":{"summary":"Get User Postgres Storage","description":"User-level Postgres storage rollup.\n\nReports the bytes the user's data occupies on disk — each channel's\nown ``ch_*`` database — and a count of the databases that number was\nmeasured from. A user without any app owns no channel row, so the\nzero-usage shape falls out without a probe. Returns 503 when the\nadmin pool / config is unavailable (degraded mode) or when any part\nof the rollup fails.\n\nThe route only reads the control DB, so it runs on\n``run_read_in_thread``. It costs one round trip to the control DB,\nand the Postgres core reads the channels' rows in a second one. The\nsizes come from the tenant Postgres.","operationId":"get_user_postgres_storage_me_postgres_storage_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":{"$ref":"#/components/schemas/UserPostgresStorage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/me/usage":{"get":{"summary":"Get User Usage","description":"Per-account database storage and current-month egress.\n\nComposes account-wide DB storage and current-month egress\n(``access_summary_daily``) alongside the plan's storage limit,\nresolved server-side from ``plans.py``. Storage comes from the same\n:func:`_user_storage_rollup` that answers ``/me/postgres/storage`` —\nthe account page renders both into ONE sentence, so the two numbers\nmust never disagree.\n\nStorage is a SOFT cap: over-limit is surfaced as a warning on the\naccount page and never blocks. Egress is a measurement only: the\npayload states the month's bytes and no allowance beside them,\nbecause no plan carries an egress figure. Returns 503 when the\nadmin pool / config is unavailable (degraded mode) or when the\nstorage rollup fails.\n\nThe route only reads the control DB, so it runs on\n``run_read_in_thread``. One statement reads the plan,\n``_user_storage_targets`` adds one more, and the Postgres core reads\nthe channels' rows in a third.","operationId":"get_user_usage_me_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":{"$ref":"#/components/schemas/UserUsage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/channels/{channel_id}/postgres/snapshots":{"get":{"summary":"List Channel Snapshots Route","description":"List the snapshots of one channel the caller can reach.\n\nThe route only reads, so it runs on ``run_read_in_thread``. It costs\nthe access check, the schema row, the snapshots, one query for the\nchannel's blob-aligned snapshot ids, and one for the channel's\nrestores. It adds one more query only when a marker row names an\ninstance, to read that instance's WAL-G window.","operationId":"list_channel_snapshots_route_apps__app_id__channels__channel_id__postgres_snapshots_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Channel 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":"array","items":{"$ref":"#/components/schemas/SnapshotListItem"},"title":"Response List Channel Snapshots Route Apps  App Id  Channels  Channel Id  Postgres Snapshots Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/channels/{channel_id}/postgres/snapshots/{snapshot_id}/download-token":{"post":{"summary":"Create Snapshot Download Token","description":"Mint a short-lived ``snapshots:read`` token for one owned snapshot.\n\nThe caller must hold ``snapshots:read`` itself: a mint never grants\nmore than the caller already holds, so an ``exports:read`` or\n``blobs:read`` download token cannot rotate into this artifact. A\ngeneral credential holds the scope by default and mints the narrow,\nsingle-purpose token. Checks run in order: ownership (404), the\n``snapshot_download_enabled`` flag (403), the snapshot row's existence\non this channel (404), and the scope (403) — so a stranger still reads\n404 before any 403, the order the download route uses. The plaintext\nis returned once and never persisted or logged; the token carries the\n``snapshot-download`` label, so it routes into its own per-label LRU\ncap.\n\nThe mint never outlives the caller either, so ``bounded_expiry``\nclamps the one-hour window to the caller's own remainder. The clamp is\nsilent: the request carries no lifetime field, so there is nothing to\nrefuse, and the response reports what the token actually holds.","operationId":"create_snapshot_download_token_apps__app_id__channels__channel_id__postgres_snapshots__snapshot_id__download_token_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Channel Id"}},{"name":"snapshot_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Snapshot Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SnapshotDownloadTokenResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/channels/{channel_id}/postgres/snapshots/{snapshot_id}/download":{"get":{"summary":"Download Channel Snapshot","description":"Serve one snapshot's ``.sqlc`` archive to a scoped bearer.\n\nThe channel load runs FIRST, so a stranger reads 404 and learns\nnothing about the channel. The flag follows it: a caller who can reach\nthe channel reads 403 ``download_disabled`` while the leg is off. The\n``snapshots:read`` scope gate follows the flag. That scope is\ndefault-granted, so a general credential serves the artifact directly —\n``GET .../postgres/dump`` already streams the LIVE database with no\nscope gate. :func:`create_snapshot_download_token` still mints a narrow\ntoken for a caller that wants a single-purpose one. The resolve,\njournal, and stream run in :func:`serve_snapshot_download`, which the\ninternal loopback route shares verbatim.","operationId":"download_channel_snapshot_apps__app_id__channels__channel_id__postgres_snapshots__snapshot_id__download_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Channel Id"}},{"name":"snapshot_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Snapshot 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"}}}}}}},"/apps/{app_id}/channels/{channel_id}/postgres/restore":{"post":{"summary":"Restore Channel Postgres","description":"Record a restore of the channel's database and answer 202.\n\nThe checks run in one transaction with the record, in this order,\nand each refusal records nothing:\n\n1. The channel: 404 for a caller who is not a member.\n2. A move that carries the app: 409 maintenance. The check runs\n   under the owner lock in its weaker mode\n   (``pg_placement.py::lock_owner_no_key``), which conflicts with the\n   ``FOR UPDATE`` every ``plan_apply`` producer takes before it\n   checks for a restore, so a restore and a plan change of one user\n   never both pass.\n3. The ``prod`` channel: 403 ``protected_action`` unless a person\n   drives the caller or the owner turned agent access on.\n4. The typed name: 400 ``invalid_confirmation``.\n5. A ``maintenance_channel_id`` with a mode other than\n   ``maintenance``: 400 ``maintenance_channel_needs_maintenance``.\n6. Degraded mode: 503 ``postgres_unavailable``.\n\nA ``request_id`` the ledger already holds then goes straight to the\ncore's verb, which replays the recorded restore, or answers 409\n``request_id_conflict`` for an id recorded for another call. A new\nrequest takes the channel's row lock\n(``channels/service.py::lock_channel``), which the tenant deploy and\nrewind, the admin rebuild, the operator redeploy, the push worker,\nand the transfer accept take before they read the restore holds, and\nmeets:\n\n7. The owner, read again under the channel lock: 409 when an\n   ownership transfer changed it since the read of check 1.\n8. The snapshot: 404 ``snapshot_not_found`` or\n   ``snapshot_host_gone``, and 409 ``snapshot_out_of_window``.\n9. The page channel, when the body names one\n   (``proxy/maintenance.py::maintenance_target_refusal``): 400\n   ``maintenance_channel_not_found`` for a channel that does not\n   exist or has another owner, ``maintenance_channel_not_static``,\n   or ``maintenance_channel_not_deployed``.\n10. The core's verb (``postgres/api.py::restore_channel_database``):\n   409 when the database is not ready, and 409\n   ``restore_in_flight`` while another restore of the channel is\n   active.\n\nOnce the core records the restore, the same transaction cancels the\nchannel's queued deploys and rewinds\n(``deploy/dispatch.py::cancel_queued_for_restore``). A running deploy\nand a queued rehome are left alone: the restore waits for them\n(design/PG-FLEET.md, \"Guards while a restore is active\").\n\nThe body's ``during_restore``, ``read_only`` by default,\n``maintenance``, or ``live``, and its ``maintenance_channel_id`` are\nparameters of the recorded command, so a replay that names another\nmode or another page channel answers ``request_id_conflict``. The\nanswer is the\nrestore's status and the ids of the deploys its start cancelled,\nwith ``Location`` naming its status route. A replay reads the\ncancelled deploys back and cancels nothing. The route then nudges the\nrestore dispatcher.","operationId":"restore_channel_postgres_apps__app_id__channels__channel_id__postgres_restore_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Channel 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/RestoreRequest"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RestoreSubmitResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/postgres/restores/{restore_id}":{"get":{"summary":"Get Postgres Restore","description":"Report one restore of a channel's database.\n\nA member of the restore's channel reads it. A restore that does not\nexist, that the 30-day ledger prune removed, whose channel is gone,\nor whose channel the caller cannot reach answers the same 404\n``restore_not_found``, so the route confirms no other tenant's\nrestore. The route only reads, so it runs on ``run_read_in_thread``.","operationId":"get_postgres_restore_postgres_restores__restore_id__get","parameters":[{"name":"restore_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Restore Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RestoreStatusResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/channels/{channel_id}/blob":{"get":{"summary":"Get Channel Blob","operationId":"get_channel_blob_apps__app_id__channels__channel_id__blob_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Channel Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChannelBlobStatus"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/me/blob/storage":{"get":{"summary":"Get User Blob Storage","operationId":"get_user_blob_storage_me_blob_storage_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":{"$ref":"#/components/schemas/UserBlobStorage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/channels/{channel_id}/blob/credentials":{"post":{"summary":"Get Channel Blob Credentials","operationId":"get_channel_blob_credentials_apps__app_id__channels__channel_id__blob_credentials_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Channel Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChannelBlobCredentials"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/channels/{channel_id}/blob/snapshots":{"get":{"summary":"List Channel Blob Snapshots","operationId":"list_channel_blob_snapshots_apps__app_id__channels__channel_id__blob_snapshots_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Channel 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":"array","items":{"$ref":"#/components/schemas/BlobSnapshotListItem"},"title":"Response List Channel Blob Snapshots Apps  App Id  Channels  Channel Id  Blob Snapshots Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/channels/{channel_id}/blob/snapshots/{snapshot_id}/download-token":{"post":{"summary":"Create Blob Snapshot Download Token","description":"Mint a short-lived ``blobs:read`` token for one owned checkpoint.\n\nThe caller must hold ``blobs:read`` itself: a mint never grants more\nthan the caller already holds, so a ``snapshots:read`` or\n``exports:read`` download token cannot rotate into this artifact. A\ngeneral credential holds the scope by default and mints the narrow,\nsingle-purpose token. Checks run in order: ownership (404), the\n``blob_snapshot_download_enabled`` flag (403), the checkpoint-id\nresolution — an unknown checkpoint answers 404 ``snapshot_not_found``,\nand a checkpoint with no aligned blob leg answers 409\n``no_aligned_blob_snapshot`` — and the scope (403), so a stranger still\nreads 404 before any 403. The plaintext is returned once and never\npersisted or logged; the token carries the ``blob-download`` label, so\nit routes into its own per-label LRU cap.\n\nThe mint never outlives the caller either, so ``bounded_expiry``\nclamps the one-hour window to the caller's own remainder. The clamp is\nsilent: the request carries no lifetime field, so there is nothing to\nrefuse, and the response reports what the token actually holds.","operationId":"create_blob_snapshot_download_token_apps__app_id__channels__channel_id__blob_snapshots__snapshot_id__download_token_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Channel Id"}},{"name":"snapshot_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Snapshot Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SnapshotDownloadTokenResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/channels/{channel_id}/blob/snapshots/{snapshot_id}/download":{"get":{"summary":"Download Channel Blob Snapshot","description":"Stream the channel's objects as they were at one checkpoint's ``T``.\n\nThe channel load runs FIRST, so a stranger reads 404 and learns\nnothing about the channel. The flag follows it: a caller who can reach\nthe channel reads 403 ``download_disabled`` while the leg is off. The\n``blobs:read`` scope gate follows the flag. That scope is\ndefault-granted, so a general credential serves the artifact directly;\n:func:`create_blob_snapshot_download_token` still mints a narrow token\nfor a caller that wants a single-purpose one.\n``snapshot_id`` is the Postgres checkpoint id; the resolve, journal, and\nstream run in :func:`serve_blob_snapshot_download`, which the internal\nloopback route shares verbatim.","operationId":"download_channel_blob_snapshot_apps__app_id__channels__channel_id__blob_snapshots__snapshot_id__download_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Channel Id"}},{"name":"snapshot_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Snapshot 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"}}}}}}},"/apps/{app_id}/channels/{channel_id}/blob/restore":{"post":{"summary":"Restore Channel Blob","operationId":"restore_channel_blob_apps__app_id__channels__channel_id__blob_restore_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Channel 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/BlobRestoreRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChannelBlobStatus"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/channels/{channel_id}/domains":{"post":{"summary":"Add Domain","operationId":"add_domain_apps__app_id__channels__channel_id__domains_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Channel 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/AddDomainRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"summary":"List Domains","operationId":"list_domains_apps__app_id__channels__channel_id__domains_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Channel Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/channels/{channel_id}/domains/{domain}/verify":{"post":{"summary":"Verify Domain","operationId":"verify_domain_apps__app_id__channels__channel_id__domains__domain__verify_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Channel Id"}},{"name":"domain","in":"path","required":true,"schema":{"type":"string","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":{"$ref":"#/components/schemas/DomainResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/channels/{channel_id}/domains/{domain}":{"delete":{"summary":"Delete Domain","operationId":"delete_domain_apps__app_id__channels__channel_id__domains__domain__delete","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Channel Id"}},{"name":"domain","in":"path","required":true,"schema":{"type":"string","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":{"$ref":"#/components/schemas/DomainDeleteResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/channels/{channel_id}/forward":{"post":{"summary":"Expose Forward","description":"Allocate (or update) the channel's public raw-TCP endpoint.\n\nRe-POSTing an already-exposed channel returns the same host/port with\n``allow_cidrs`` replaced by the submitted list, and ``200`` instead of\n``201`` — the endpoint address is stable for the life of the exposure\nso tenants can hand it out.","operationId":"expose_forward_apps__app_id__channels__channel_id__forward_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Channel 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/ExposeForwardRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForwardResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Unexpose Forward","description":"Release the channel's public endpoint. ``404`` when there is none.","operationId":"unexpose_forward_apps__app_id__channels__channel_id__forward_delete","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Channel Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/forwards":{"get":{"summary":"List Forwards","description":"Every exposed channel of the app, in channel-name order.","operationId":"list_forwards_apps__app_id__forwards_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForwardListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/on-demand-tls/check":{"get":{"summary":"Check On Demand Tls","description":"Approve on-demand TLS issuance for verified custom domains.\n\nDeliberately unauthenticated and public: the only information it\nleaks is a boolean \"is this domain attached and verified?\" — an\nattacker learns nothing they couldn't learn by connecting to the\nhostname over TLS. One indexed SELECT per call (``domain`` has a\nunique index), so a probe flood costs one point lookup per call.","operationId":"check_on_demand_tls_on_demand_tls_check_get","parameters":[{"name":"domain","in":"query","required":true,"schema":{"type":"string","title":"Domain"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/members":{"get":{"summary":"List Members","operationId":"list_members_apps__app_id__members_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MemberListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"summary":"Add Member","description":"Invite a user (neutral). See :func:`membership.invite_member`.","operationId":"add_member_apps__app_id__members_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App 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/AddMemberRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InviteResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/members/{user_id}":{"patch":{"summary":"Set Member Role","operationId":"set_member_role_apps__app_id__members__user_id__patch","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"user_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","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/SetRoleRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MemberResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Remove Member","operationId":"remove_member_apps__app_id__members__user_id__delete","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"user_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","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","additionalProperties":{"type":"boolean"},"title":"Response Remove Member Apps  App Id  Members  User Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/invites":{"get":{"summary":"List Invites","description":"List pending project invitations addressed to the caller.","operationId":"list_invites_invites_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":{"$ref":"#/components/schemas/InviteListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/invite/accept":{"post":{"summary":"Accept Invite","description":"Accept a pending invite addressed to the caller (idempotent).\n\nThe gate reads the CALLER's account default, never the app owner's:\nthe new membership lands on the caller's account, and the person who\nsent the invite must not hold the switch that protects the person\nwho reads it. The route loads no ``App`` row for the gate, because a\npending invitee cannot pass ``load_app_for``.","operationId":"accept_invite_apps__app_id__invite_accept_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App 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","additionalProperties":{"type":"string"},"title":"Response Accept Invite Apps  App Id  Invite Accept Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/invite/decline":{"post":{"summary":"Decline Invite","description":"Decline a pending invite addressed to the caller (idempotent).\n\nGated on the caller's own account default, the rule of\n:func:`accept_invite`.","operationId":"decline_invite_apps__app_id__invite_decline_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App 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","additionalProperties":{"type":"string"},"title":"Response Decline Invite Apps  App Id  Invite Decline Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/transfer":{"post":{"summary":"Transfer Ownership","description":"Offer the project to an existing member. See :func:`perform_transfer`.","operationId":"transfer_ownership_apps__app_id__transfer_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App 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/TransferRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransferOfferResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/transfer/cancel":{"post":{"summary":"Cancel Transfer Offer","description":"Withdraw the project's outstanding offer (owner only, idempotent).","operationId":"cancel_transfer_offer_apps__app_id__transfer_cancel_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App 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","additionalProperties":{"type":"string"},"title":"Response Cancel Transfer Offer Apps  App Id  Transfer Cancel Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/transfer/accept":{"post":{"summary":"Accept Transfer Offer","description":"Accept the offer addressed to the caller.\n\nSee :func:`perform_transfer_accept` for the re-validation, the cap,\nand the disk-move-first ordering.","operationId":"accept_transfer_offer_apps__app_id__transfer_accept_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App 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","additionalProperties":{"type":"string"},"title":"Response Accept Transfer Offer Apps  App Id  Transfer Accept Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/transfer/decline":{"post":{"summary":"Decline Transfer Offer","description":"Decline the offer addressed to the caller (idempotent).","operationId":"decline_transfer_offer_apps__app_id__transfer_decline_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App 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","additionalProperties":{"type":"string"},"title":"Response Decline Transfer Offer Apps  App Id  Transfer Decline Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/transfers":{"get":{"summary":"List Transfers","description":"List the outstanding ownership offers addressed to the caller.\n\nA route of its own, never a widening of ``GET /invites``: an offer\ncarries no role, and answering one is a far heavier act than\nanswering an invite.","operationId":"list_transfers_transfers_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":{"$ref":"#/components/schemas/TransferListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/events":{"get":{"summary":"List Events","operationId":"list_events_apps__app_id__events_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"before","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Before"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/github/connect":{"post":{"summary":"Connect Github","operationId":"connect_github_apps__app_id__github_connect_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App 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/ConnectRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/github":{"get":{"summary":"Get Github","operationId":"get_github_apps__app_id__github_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatusResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Disconnect Github","operationId":"disconnect_github_apps__app_id__github_delete","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{app_id}/github/sync":{"post":{"summary":"Sync Github","description":"Fetch the GitHub remote now, and answer the status this sync left.\n\nThe response is the row's static fields overlaid with THIS request's\noutcome, never a re-read of the outcome columns: a concurrent sync\ncan write them in between. A busy repo and a frozen app answer\n503 from :func:`run_mirror_sync`, which then writes no row and this\nroute journals nothing.","operationId":"sync_github_apps__app_id__github_sync_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatusResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/webhooks/github/{app_id}":{"post":{"summary":"Github Push Ping","description":"Record a push ping for ``app_id`` and wake the push worker.\n\nUnauthenticated by design (module docstring). The key of the rate\ncap is the source IP: Caddy overwrites ``X-Forwarded-For``, and only\nCaddy reaches this listener, so the value is the real peer. The cap\nis DoS hygiene, not an authorization boundary.","operationId":"github_push_ping_webhooks_github__app_id__post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"App Id"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/exports":{"post":{"summary":"Create Export","operationId":"create_export_exports_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/CreateExportRequest"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"summary":"List Exports","description":"List the exports of the caller, newest first.\n\nThe route only reads, so it runs on ``run_read_in_thread``. It costs\none round trip to the control DB.","operationId":"list_exports_exports_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":"array","items":{"$ref":"#/components/schemas/ExportResponse"},"title":"Response List Exports Exports Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/exports/{export_id}":{"get":{"summary":"Get Export","description":"Report one export the caller owns.\n\nThe route only reads, so it runs on ``run_read_in_thread``. It costs\none round trip to the control DB.","operationId":"get_export_exports__export_id__get","parameters":[{"name":"export_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Export Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/exports/{export_id}/download-token":{"post":{"summary":"Create Export Download Token","description":"Mint an on-demand ``exports:read`` token for a ready, owned export.\n\nOwnership + readiness are enforced by :func:`resolve_export_download`\n(404 on non-owner / not-ready), mirroring the download routes. The\nscope gate follows them, exactly as on the download routes, so a\nstranger reads 404 before any 403. The caller must hold\n``exports:read`` itself: a mint never grants more than the caller\nalready holds, so a ``snapshots:read`` or ``blobs:read`` download\ntoken cannot rotate into this artifact. The plaintext is returned once\nand never persisted or logged; the token carries the same label the\nbuilder's ``_finalize`` uses, so the sweeper's existing revocation\ncovers it.\n\nThe mint never outlives the caller either, so ``bounded_expiry``\nholds the token to the earlier of the export's expiry and the\ncaller's own. That also answers the export row whose ``expires_at``\nis NULL: the token takes the caller's expiry instead of living\nforever. The clamp is silent, because the request carries no lifetime\nfield. The response reports the token's expiry, which is what governs\nthe download the caller is about to run.","operationId":"create_export_download_token_exports__export_id__download_token_post","parameters":[{"name":"export_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Export Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DownloadTokenResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/exports/{export_id}/download":{"get":{"summary":"Download Export","operationId":"download_export_exports__export_id__download_get","parameters":[{"name":"export_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Export 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"}}}}}}},"/exports/{export_id}/download/blobs":{"get":{"summary":"Download Export Blobs","description":"Live-stream ``blobs.tar.gz`` from the version-pinned inventory.\n\nBearer + ``exports:read`` + ownership, then the threshold gate: an\nover-threshold inventory returns 409 (the export was flagged\nincomplete at build time). Every key/version/bucket streamed comes\nONLY from the staged inventory — the route never accepts a\nclient-supplied key, version, or bucket (SSRF / cross-tenant guard).","operationId":"download_export_blobs_exports__export_id__download_blobs_get","parameters":[{"name":"export_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Export 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"}}}}}}}},"components":{"schemas":{"AddDomainRequest":{"properties":{"domain":{"type":"string","title":"Domain"}},"type":"object","required":["domain"],"title":"AddDomainRequest"},"AddMemberRequest":{"properties":{"username_or_email":{"type":"string","title":"Username Or Email"},"role":{"type":"string","enum":["admin","member"],"title":"Role"}},"type":"object","required":["username_or_email","role"],"title":"AddMemberRequest"},"AddThreadNoteRequest":{"properties":{"body":{"type":"string","title":"Body"},"agent_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent Id"}},"type":"object","required":["body"],"title":"AddThreadNoteRequest"},"AppFeedbackResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","created_at"],"title":"AppFeedbackResponse"},"AppListResponse":{"properties":{"apps":{"items":{"$ref":"#/components/schemas/AppResponse"},"type":"array","title":"Apps"}},"type":"object","required":["apps"],"title":"AppListResponse"},"AppResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"name":{"type":"string","title":"Name"},"repo_url":{"type":"string","title":"Repo Url"},"template":{"type":"string","title":"Template"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"external_db_access_enabled":{"type":"boolean","title":"External Db Access Enabled"},"external_blob_access_enabled":{"type":"boolean","title":"External Blob Access Enabled"},"port_forwarding_enabled":{"type":"boolean","title":"Port Forwarding Enabled"},"port_forwarding_available":{"type":"boolean","title":"Port Forwarding Available"},"agent_protected_actions_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Agent Protected Actions Enabled"},"agent_protected_actions_effective":{"type":"boolean","title":"Agent Protected Actions Effective"},"channels":{"items":{"$ref":"#/components/schemas/ChannelResponse"},"type":"array","title":"Channels"},"owner_username":{"type":"string","title":"Owner Username"},"role":{"type":"string","title":"Role"}},"type":"object","required":["id","name","repo_url","template","created_at","external_db_access_enabled","external_blob_access_enabled","port_forwarding_enabled","port_forwarding_available","agent_protected_actions_enabled","agent_protected_actions_effective","channels","owner_username","role"],"title":"AppResponse"},"BlobRestoreRequest":{"properties":{"confirm_channel_name":{"type":"string","title":"Confirm Channel Name"},"snapshot_id":{"type":"string","format":"uuid","title":"Snapshot Id"}},"type":"object","required":["confirm_channel_name","snapshot_id"],"title":"BlobRestoreRequest"},"BlobSnapshotListItem":{"properties":{"snapshot_id":{"type":"string","format":"uuid","title":"Snapshot Id"},"deploy_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Deploy Id"},"kind":{"type":"string","title":"Kind"},"pg_snapshot_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Pg Snapshot Id"},"snapshot_ts":{"type":"string","format":"date-time","title":"Snapshot Ts"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["snapshot_id","deploy_id","kind","pg_snapshot_id","snapshot_ts","created_at"],"title":"BlobSnapshotListItem","description":"One object-store restore point of a channel.\n\n``kind`` is ``pre_deploy`` or ``nightly`` and tells the user which\nretention rule holds the row. ``deploy_id`` is null on a ``nightly``\nrow, because no deploy triggers one. ``pg_snapshot_id`` names the\nPostgres snapshot of the same checkpoint, so a caller can ask both\ndownloads for legs that match. It is null on an unaligned row."},"Body_authorize_post_oauth_authorize_post":{"properties":{"client_id":{"type":"string","title":"Client Id","default":""},"redirect_uri":{"type":"string","title":"Redirect Uri","default":""},"code_challenge":{"type":"string","title":"Code Challenge","default":""},"code_challenge_method":{"type":"string","title":"Code Challenge Method","default":""},"state":{"type":"string","title":"State","default":""},"scope":{"type":"string","title":"Scope","default":""},"action":{"type":"string","title":"Action","default":""}},"type":"object","title":"Body_authorize_post_oauth_authorize_post"},"Body_revoke_oauth_revoke_post":{"properties":{"token":{"type":"string","title":"Token","default":""},"token_type_hint":{"type":"string","title":"Token Type Hint","default":""}},"type":"object","title":"Body_revoke_oauth_revoke_post"},"Body_token_endpoint_oauth_token_post":{"properties":{"grant_type":{"type":"string","title":"Grant Type","default":""},"code":{"type":"string","title":"Code","default":""},"redirect_uri":{"type":"string","title":"Redirect Uri","default":""},"client_id":{"type":"string","title":"Client Id","default":""},"code_verifier":{"type":"string","title":"Code Verifier","default":""},"refresh_token":{"type":"string","title":"Refresh Token","default":""}},"type":"object","title":"Body_token_endpoint_oauth_token_post"},"BuildHealth":{"properties":{"available":{"type":"boolean","title":"Available"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"},"oom_kill_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Oom Kill Count"},"last_oom_kill_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Oom Kill At"}},"type":"object","required":["available","reason","oom_kill_count","last_oom_kill_at"],"title":"BuildHealth","description":"Recent builds of this channel that the kernel stopped."},"CastVoteRequest":{"properties":{"value":{"type":"integer","enum":[-1,0,1],"title":"Value"}},"type":"object","required":["value"],"title":"CastVoteRequest"},"ChangesetRequest":{"properties":{"ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ref"},"message":{"type":"string","title":"Message"},"changes":{"additionalProperties":{"anyOf":[{"type":"string"},{"type":"null"}]},"type":"object","title":"Changes"},"edits":{"additionalProperties":{"items":{"$ref":"#/components/schemas/EditRequest"},"type":"array"},"type":"object","title":"Edits"},"patches":{"additionalProperties":{"type":"string"},"type":"object","title":"Patches"}},"type":"object","required":["message"],"title":"ChangesetRequest"},"ChangesetResponse":{"properties":{"sha":{"type":"string","title":"Sha"}},"type":"object","required":["sha"],"title":"ChangesetResponse"},"ChannelBlobCredentials":{"properties":{"access_key_id":{"type":"string","title":"Access Key Id"},"secret_access_key":{"type":"string","title":"Secret Access Key"},"endpoint":{"type":"string","title":"Endpoint"},"region":{"type":"string","title":"Region"},"bucket":{"type":"string","title":"Bucket"}},"type":"object","required":["access_key_id","secret_access_key","endpoint","region","bucket"],"title":"ChannelBlobCredentials"},"ChannelBlobStatus":{"properties":{"status":{"type":"string","enum":["provisioning","ready","failed"],"title":"Status"},"last_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Error"},"usage_bytes":{"type":"integer","title":"Usage Bytes"},"usage_measured_at":{"type":"string","format":"date-time","title":"Usage Measured At"},"external_enabled":{"type":"boolean","title":"External Enabled"},"virtual_bucket":{"type":"string","title":"Virtual Bucket"},"virtual_endpoint":{"type":"string","title":"Virtual Endpoint"},"region":{"type":"string","title":"Region"}},"type":"object","required":["status","last_error","usage_bytes","usage_measured_at","external_enabled","virtual_bucket","virtual_endpoint","region"],"title":"ChannelBlobStatus"},"ChannelHealth":{"properties":{"app_id":{"type":"string","format":"uuid","title":"App Id"},"channel_id":{"type":"string","format":"uuid","title":"Channel Id"},"resource":{"$ref":"#/components/schemas/ResourceHealth"},"runtime":{"$ref":"#/components/schemas/RuntimeHealth"},"build":{"$ref":"#/components/schemas/BuildHealth"},"database":{"$ref":"#/components/schemas/DatabaseHealth"},"latency":{"$ref":"#/components/schemas/LatencyHealth"},"findings":{"items":{"$ref":"#/components/schemas/Finding"},"type":"array","title":"Findings"}},"type":"object","required":["app_id","channel_id","resource","runtime","build","database","latency","findings"],"title":"ChannelHealth","description":"The whole diagnosis of one channel."},"ChannelImage":{"properties":{"tag":{"type":"string","title":"Tag"},"sha":{"type":"string","title":"Sha"},"size_bytes":{"type":"integer","title":"Size Bytes"},"charged_size_bytes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Charged Size Bytes"},"matched_base":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Matched Base"},"created":{"type":"integer","title":"Created"},"current":{"type":"boolean","title":"Current"}},"type":"object","required":["tag","sha","size_bytes","charged_size_bytes","matched_base","created","current"],"title":"ChannelImage"},"ChannelImagesResponse":{"properties":{"images":{"anyOf":[{"items":{"$ref":"#/components/schemas/ChannelImage"},"type":"array"},{"type":"null"}],"title":"Images"},"image_cap_bytes":{"type":"integer","title":"Image Cap Bytes"}},"type":"object","required":["images","image_cap_bytes"],"title":"ChannelImagesResponse"},"ChannelPostgresStatus":{"properties":{"db_name":{"type":"string","title":"Db Name"},"role_name":{"type":"string","title":"Role Name"},"status":{"type":"string","enum":["provisioning","ready","failed"],"title":"Status"},"last_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Error"},"connection_count":{"type":"integer","title":"Connection Count"},"connection_limit":{"type":"integer","title":"Connection Limit"},"password_set":{"type":"boolean","title":"Password Set"},"storage_bytes":{"type":"integer","title":"Storage Bytes"},"external_enabled":{"type":"boolean","title":"External Enabled"},"external_host":{"type":"string","title":"External Host"},"external_database":{"type":"string","title":"External Database"},"restore":{"anyOf":[{"$ref":"#/components/schemas/RestoreStatusResponse"},{"type":"null"}]}},"type":"object","required":["db_name","role_name","status","last_error","connection_count","connection_limit","password_set","storage_bytes","external_enabled","external_host","external_database","restore"],"title":"ChannelPostgresStatus"},"ChannelResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"name":{"type":"string","title":"Name"},"hostname":{"type":"string","title":"Hostname"},"git_ref_binding":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Git Ref Binding"},"follows_branch":{"type":"boolean","title":"Follows Branch"},"current_sha":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Current Sha"},"status":{"type":"string","title":"Status"},"pending_deploy":{"anyOf":[{"$ref":"#/components/schemas/PendingDeployResponse"},{"type":"null"}]}},"type":"object","required":["id","name","hostname","git_ref_binding","follows_branch","current_sha","status","pending_deploy"],"title":"ChannelResponse"},"CheckoutRequest":{"properties":{"tier":{"type":"string","title":"Tier"}},"type":"object","required":["tier"],"title":"CheckoutRequest"},"ConnectRequest":{"properties":{"remote_url":{"type":"string","title":"Remote Url"}},"type":"object","required":["remote_url"],"title":"ConnectRequest"},"ConnectResponse":{"properties":{"public_key":{"type":"string","title":"Public Key"},"remote_url":{"type":"string","title":"Remote Url"}},"type":"object","required":["public_key","remote_url"],"title":"ConnectResponse"},"CreateAppRequest":{"properties":{"name":{"type":"string","title":"Name"},"template":{"type":"string","title":"Template","default":"static"},"branch":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Branch"}},"additionalProperties":false,"type":"object","required":["name"],"title":"CreateAppRequest","description":"The body of POST /apps.\n\nThe scheduler places every app, so the body names no host.\n``extra=\"forbid\"`` makes any other field, such as ``host_id``, a 422\nthat names it, and FastAPI returns that 422 before the route creates\nanything."},"CreateChannelRequest":{"properties":{"name":{"type":"string","title":"Name"},"git_ref_binding":{"type":"string","title":"Git Ref Binding"}},"type":"object","required":["name","git_ref_binding"],"title":"CreateChannelRequest"},"CreateCredentialsRequest":{"properties":{"scopes":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Scopes"},"expires_in":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Expires In"}},"type":"object","title":"CreateCredentialsRequest"},"CreateExportRequest":{"properties":{"scope":{"type":"string","title":"Scope"},"app_id":{"type":"string","format":"uuid","title":"App Id"},"channel_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Channel Id"}},"type":"object","required":["scope","app_id"],"title":"CreateExportRequest"},"CreateForwardNodeRequest":{"properties":{"name":{"type":"string","title":"Name"},"hostname":{"type":"string","title":"Hostname"},"port_range_start":{"type":"integer","title":"Port Range Start"},"port_range_end":{"type":"integer","title":"Port Range End"}},"type":"object","required":["name","hostname","port_range_start","port_range_end"],"title":"CreateForwardNodeRequest"},"CreateHostRequest":{"properties":{"public_fqdn":{"type":"string","title":"Public Fqdn"},"provider":{"type":"string","title":"Provider"},"site":{"type":"string","title":"Site"},"machine_class":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Machine Class"}},"type":"object","required":["public_fqdn","provider","site"],"title":"CreateHostRequest"},"CreateHostResponse":{"properties":{"host_id":{"type":"string","format":"uuid","title":"Host Id"},"name":{"type":"string","title":"Name"},"overlay_ip":{"type":"string","title":"Overlay Ip"},"enrollment_token":{"type":"string","title":"Enrollment Token"}},"type":"object","required":["host_id","name","overlay_ip","enrollment_token"],"title":"CreateHostResponse"},"CreateInstanceRequest":{"properties":{"host_id":{"type":"string","format":"uuid","title":"Host Id"},"profile":{"type":"string","title":"Profile"},"owner_user_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Owner User Id"},"request_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Request Id"},"force":{"type":"boolean","title":"Force","default":false}},"type":"object","required":["host_id","profile"],"title":"CreateInstanceRequest"},"CreateReplicaRequest":{"properties":{"host_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Host Id"},"request_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Request Id"}},"type":"object","title":"CreateReplicaRequest"},"CreateSshKeyRequest":{"properties":{"public_key":{"type":"string","title":"Public Key"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"api_login":{"type":"boolean","title":"Api Login","default":false}},"additionalProperties":false,"type":"object","required":["public_key"],"title":"CreateSshKeyRequest"},"CreateThreadRequest":{"properties":{"subject":{"type":"string","title":"Subject"},"body":{"type":"string","title":"Body"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"channel_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Channel Id"},"branch":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Branch"},"sha":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sha"},"agent_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent Id"}},"type":"object","required":["subject","body"],"title":"CreateThreadRequest"},"CreateThreadResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"number":{"type":"integer","title":"Number"},"subject":{"type":"string","title":"Subject"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"channel_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Channel Id"},"branch":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Branch"},"sha":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sha"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"note":{"$ref":"#/components/schemas/NoteResponse"}},"type":"object","required":["id","number","subject","category","channel_id","branch","sha","created_at","note"],"title":"CreateThreadResponse"},"CreateTokenRequest":{"properties":{"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"expires_in":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Expires In"}},"type":"object","title":"CreateTokenRequest"},"CreateTokenResponse":{"properties":{"token_id":{"type":"string","format":"uuid","title":"Token Id"},"plaintext":{"type":"string","title":"Plaintext"},"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At"}},"type":"object","required":["token_id","plaintext","scopes","label","created_at","expires_at"],"title":"CreateTokenResponse"},"CredentialsResponse":{"properties":{"token":{"type":"string","title":"Token"},"username":{"type":"string","title":"Username"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"},"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes"}},"type":"object","required":["token","username","expires_at","scopes"],"title":"CredentialsResponse"},"DatabaseHealth":{"properties":{"available":{"type":"boolean","title":"Available"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"},"size_bytes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Size Bytes"},"cache_hit_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cache Hit Pct"},"top_statements":{"$ref":"#/components/schemas/StatementsHealth"}},"type":"object","required":["available","reason","size_bytes","cache_hit_pct","top_statements"],"title":"DatabaseHealth","description":"Size, cache-hit percent and top statements of the channel DB."},"DeployEnvSnapshotItem":{"properties":{"key":{"type":"string","title":"Key"},"kind":{"type":"string","enum":["env","secret"],"title":"Kind"},"source":{"type":"string","enum":["app","channel"],"title":"Source"},"value":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Value"}},"type":"object","required":["key","kind","source","value"],"title":"DeployEnvSnapshotItem","description":"One user-set var from a deploy's env snapshot.\n\n``value`` is the plaintext for ``kind='env'`` rows and always\n``None`` for ``kind='secret'`` — the same non-leak rule as the\nlist route; snapshot views never reveal secret values."},"DeployEnvSnapshotResponse":{"properties":{"deploy_id":{"type":"string","format":"uuid","title":"Deploy Id"},"env":{"items":{"$ref":"#/components/schemas/DeployEnvSnapshotItem"},"type":"array","title":"Env"},"system_keys":{"items":{"type":"string"},"type":"array","title":"System Keys"}},"type":"object","required":["deploy_id","env","system_keys"],"title":"DeployEnvSnapshotResponse"},"DeployHistoryItem":{"properties":{"deploy_id":{"type":"string","format":"uuid","title":"Deploy Id"},"git_sha":{"type":"string","title":"Git Sha"},"status":{"type":"string","title":"Status"},"started_at":{"type":"string","format":"date-time","title":"Started At"},"finished_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Finished At"}},"type":"object","required":["deploy_id","git_sha","status","started_at","finished_at"],"title":"DeployHistoryItem"},"DeployLogResponse":{"properties":{"deploy_id":{"type":"string","format":"uuid","title":"Deploy Id"},"git_sha":{"type":"string","title":"Git Sha"},"status":{"type":"string","title":"Status"},"superseded_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Superseded By"},"started_at":{"type":"string","format":"date-time","title":"Started At"},"finished_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Finished At"},"log_bytes":{"type":"integer","title":"Log Bytes"},"offset":{"type":"integer","title":"Offset"},"window_bytes":{"type":"integer","title":"Window Bytes"},"log":{"type":"string","title":"Log"}},"type":"object","required":["deploy_id","git_sha","status","superseded_by","started_at","finished_at","log_bytes","offset","window_bytes","log"],"title":"DeployLogResponse","description":"One deploy's status plus a byte window of its build log."},"DeployRequest":{"properties":{"sha":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sha"},"ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ref"},"request_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Request Id"},"expected_current_sha":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expected Current Sha"}},"type":"object","title":"DeployRequest"},"DeployResponse":{"properties":{"deploy_id":{"type":"string","format":"uuid","title":"Deploy Id"},"channel_id":{"type":"string","format":"uuid","title":"Channel Id"},"status":{"type":"string","title":"Status"},"joined":{"type":"boolean","title":"Joined","default":false}},"type":"object","required":["deploy_id","channel_id","status"],"title":"DeployResponse"},"DnsRecords":{"properties":{"txt_host":{"type":"string","title":"Txt Host"},"txt_value":{"type":"string","title":"Txt Value"},"cname_target":{"type":"string","title":"Cname Target"},"a_values":{"items":{"type":"string"},"type":"array","title":"A Values"}},"type":"object","required":["txt_host","txt_value","cname_target","a_values"],"title":"DnsRecords"},"DomainDeleteResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"notice":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notice"}},"type":"object","required":["ok","notice"],"title":"DomainDeleteResponse"},"DomainListResponse":{"properties":{"domains":{"items":{"$ref":"#/components/schemas/DomainResponse"},"type":"array","title":"Domains"}},"type":"object","required":["domains"],"title":"DomainListResponse"},"DomainResponse":{"properties":{"domain":{"type":"string","title":"Domain"},"status":{"type":"string","title":"Status"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"},"dns_records":{"$ref":"#/components/schemas/DnsRecords"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"verified_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Verified At"},"notice":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notice"}},"type":"object","required":["domain","status","reason","dns_records","created_at","verified_at","notice"],"title":"DomainResponse"},"DownloadTokenResponse":{"properties":{"token":{"type":"string","title":"Token"},"download_url":{"type":"string","title":"Download Url"},"blobs_download_url":{"type":"string","title":"Blobs Download Url"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At"},"blobs_included":{"type":"boolean","title":"Blobs Included"},"blobs_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Blobs Reason"}},"type":"object","required":["token","download_url","blobs_download_url","expires_at","blobs_included","blobs_reason"],"title":"DownloadTokenResponse"},"DuringRestore":{"type":"string","enum":["read_only","maintenance","live"],"title":"DuringRestore","description":"What the channel's app can do with its database while the restore\nruns, from the claim to the swap.\n\n``read_only``: the ``hold`` step makes every new transaction of\n``r_<ch32>`` in ``ch_<ch32>`` read-only, so a write fails with\nSQLSTATE 25006 rather than vanish at the swap. ``maintenance``: the\nsame hold, and the edge serves a page in place of the app\n(``proxy/maintenance.py``), the restore's ``maintenance_channel_id``\nparameter naming a static channel for the page or ``None`` for the\nplatform's own. ``live``: the app keeps writing, and the swap\ndiscards those writes."},"EditRequest":{"properties":{"old_string":{"type":"string","minLength":1,"title":"Old String"},"new_string":{"type":"string","title":"New String"},"replace_all":{"type":"boolean","title":"Replace All","default":false}},"additionalProperties":false,"type":"object","required":["old_string","new_string"],"title":"EditRequest","description":"One anchored replacement. Contract: xhost_git.edits.\n\n``extra=\"forbid\"`` so a misspelled field is a 422 naming it, not a\nsilently dropped flag — ``replaceAll`` would otherwise degrade to the\nunique-or-fail default one round trip later."},"EnvKind":{"type":"string","enum":["env","secret"],"title":"EnvKind"},"EnvListItem":{"properties":{"key":{"type":"string","title":"Key"},"kind":{"type":"string","enum":["env","secret"],"title":"Kind"},"scope":{"type":"string","enum":["app","channel"],"title":"Scope"},"channel_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Channel Id"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"value":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Value"}},"type":"object","required":["key","kind","scope","channel_id","updated_at","value"],"title":"EnvListItem","description":"One env row in a list response.\n\n``value`` is the decrypted plaintext for ``kind='env'`` rows and is\nalways ``None`` for ``kind='secret'`` rows — secret values never\nappear in a list response; the reveal endpoint\n(``GET /apps/{app_id}/env/{key}/value``) is the only read path."},"EnvListResponse":{"properties":{"env":{"items":{"$ref":"#/components/schemas/EnvListItem"},"type":"array","title":"Env"}},"type":"object","required":["env"],"title":"EnvListResponse"},"EnvValueResponse":{"properties":{"key":{"type":"string","title":"Key"},"kind":{"type":"string","enum":["env","secret"],"title":"Kind"},"scope":{"type":"string","enum":["app","channel"],"title":"Scope"},"value":{"type":"string","title":"Value"}},"type":"object","required":["key","kind","scope","value"],"title":"EnvValueResponse"},"EventListResponse":{"properties":{"events":{"items":{"$ref":"#/components/schemas/EventResponse"},"type":"array","title":"Events"},"next_before":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Next Before"}},"type":"object","required":["events","next_before"],"title":"EventListResponse"},"EventResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"actor_username":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Actor Username"},"action":{"type":"string","title":"Action"},"target":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target"},"detail":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Detail"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","actor_username","action","target","detail","created_at"],"title":"EventResponse"},"ExportResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"scope":{"type":"string","title":"Scope"},"app_id":{"type":"string","format":"uuid","title":"App Id"},"channel_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Channel Id"},"status":{"type":"string","title":"Status"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"progress_pct":{"type":"integer","title":"Progress Pct"},"size_bytes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Size Bytes"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"blobs_included":{"type":"boolean","title":"Blobs Included"},"blobs_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Blobs Reason"},"blob_object_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Blob Object Count"},"blob_bytes_estimate":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Blob Bytes Estimate"},"created_at":{"type":"string","title":"Created At"},"finished_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Finished At"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At"}},"type":"object","required":["id","scope","app_id","channel_id","status","detail","progress_pct","size_bytes","error","blobs_included","blobs_reason","blob_object_count","blob_bytes_estimate","created_at","finished_at","expires_at"],"title":"ExportResponse"},"ExposeForwardRequest":{"properties":{"allow_cidrs":{"items":{"type":"string"},"type":"array","title":"Allow Cidrs","default":[]}},"type":"object","title":"ExposeForwardRequest"},"ExternalBlobAccessRequest":{"properties":{"enabled":{"type":"boolean","title":"Enabled"}},"type":"object","required":["enabled"],"title":"ExternalBlobAccessRequest"},"ExternalDbAccessRequest":{"properties":{"enabled":{"type":"boolean","title":"Enabled"}},"type":"object","required":["enabled"],"title":"ExternalDbAccessRequest"},"FeedbackNoteListResponse":{"properties":{"notes":{"items":{"$ref":"#/components/schemas/FeedbackNoteResponse"},"type":"array","title":"Notes"}},"type":"object","required":["notes"],"title":"FeedbackNoteListResponse"},"FeedbackNoteResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"body":{"type":"string","title":"Body"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"author_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Author Label"}},"type":"object","required":["id","body","category","created_at","author_label"],"title":"FeedbackNoteResponse","description":"One external feedback note on the member read surface.\n\nThe ``body`` is untrusted third-party data: no surface renders it as\nmarkup, and no agent obeys it as an instruction. The payload carries\n``author_label`` for attribution and never an author id or an\n``agent_id``."},"Finding":{"properties":{"code":{"type":"string","title":"Code"},"severity":{"type":"string","enum":["info","warning","critical"],"title":"Severity"},"what":{"type":"string","title":"What"},"why":{"type":"string","title":"Why"},"action":{"$ref":"#/components/schemas/FindingAction"}},"type":"object","required":["code","severity","what","why","action"],"title":"Finding","description":"One cause the route found, with the rule that cause carries."},"FindingAction":{"properties":{"do":{"type":"string","enum":["wait","retry","change_code","upgrade_plan","contact_support","none"],"title":"Do"},"actor":{"type":"string","enum":["agent","user"],"title":"Actor"},"retry_after_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Retry After Seconds"}},"type":"object","required":["do","actor","retry_after_seconds"],"title":"FindingAction","description":"What to do about one finding, and who does it.\n\n``do`` is a closed set, so a reader never parses prose to learn\nwhether a machine may try again. ``retry_after_seconds`` is\nnecessary for ``retry`` and for ``wait``, and forbidden for every\nother verb."},"ForwardListResponse":{"properties":{"forwards":{"items":{"$ref":"#/components/schemas/ForwardResponse"},"type":"array","title":"Forwards"}},"type":"object","required":["forwards"],"title":"ForwardListResponse"},"ForwardNodeInfo":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"name":{"type":"string","title":"Name"},"hostname":{"type":"string","title":"Hostname"},"port_range_start":{"type":"integer","title":"Port Range Start"},"port_range_end":{"type":"integer","title":"Port Range End"},"enabled":{"type":"boolean","title":"Enabled"},"allocated_count":{"type":"integer","title":"Allocated Count"},"capacity":{"type":"integer","title":"Capacity"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","name","hostname","port_range_start","port_range_end","enabled","allocated_count","capacity","created_at"],"title":"ForwardNodeInfo"},"ForwardNodeListResponse":{"properties":{"nodes":{"items":{"$ref":"#/components/schemas/ForwardNodeInfo"},"type":"array","title":"Nodes"}},"type":"object","required":["nodes"],"title":"ForwardNodeListResponse"},"ForwardResponse":{"properties":{"channel_id":{"type":"string","format":"uuid","title":"Channel Id"},"channel":{"type":"string","title":"Channel"},"host":{"type":"string","title":"Host"},"port":{"type":"integer","title":"Port"},"allow_cidrs":{"items":{"type":"string"},"type":"array","title":"Allow Cidrs"},"active":{"type":"boolean","title":"Active"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["channel_id","channel","host","port","allow_cidrs","active","created_at"],"title":"ForwardResponse"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HaModeRequest":{"properties":{"ha_mode":{"type":"string","title":"Ha Mode"},"request_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Request Id"}},"type":"object","required":["ha_mode"],"title":"HaModeRequest"},"HostInfo":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"name":{"type":"string","title":"Name"},"public_fqdn":{"type":"string","title":"Public Fqdn"},"agent_endpoint":{"type":"string","title":"Agent Endpoint"},"enrollment_state":{"type":"string","title":"Enrollment State"},"last_seen_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Seen At"},"overlay_ip":{"type":"string","title":"Overlay Ip"},"git_endpoint":{"type":"string","title":"Git Endpoint"},"machine_class":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Machine Class"},"mem_budget_mb":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Mem Budget Mb"},"reserved_mem_mb":{"type":"integer","title":"Reserved Mem Mb"},"users_by_plan":{"additionalProperties":{"type":"integer"},"type":"object","title":"Users By Plan"},"user_count":{"type":"integer","title":"User Count"},"app_count":{"type":"integer","title":"App Count"},"running_channels":{"type":"integer","title":"Running Channels"},"total_channels":{"type":"integer","title":"Total Channels"},"resident_databases":{"type":"integer","title":"Resident Databases"},"code_stamp":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Code Stamp"}},"type":"object","required":["id","name","public_fqdn","agent_endpoint","enrollment_state","last_seen_at","overlay_ip","git_endpoint","machine_class","mem_budget_mb","reserved_mem_mb","users_by_plan","user_count","app_count","running_channels","total_channels","resident_databases","code_stamp"],"title":"HostInfo"},"InstanceCommandRequest":{"properties":{"request_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Request Id"}},"type":"object","title":"InstanceCommandRequest"},"InviteListResponse":{"properties":{"invites":{"items":{"$ref":"#/components/schemas/InviteSummary"},"type":"array","title":"Invites"}},"type":"object","required":["invites"],"title":"InviteListResponse"},"InviteResponse":{"properties":{"status":{"type":"string","title":"Status","default":"invited"}},"type":"object","title":"InviteResponse"},"InviteSummary":{"properties":{"app_id":{"type":"string","format":"uuid","title":"App Id"},"app_name":{"type":"string","title":"App Name"},"role":{"type":"string","title":"Role"},"owner_username":{"type":"string","title":"Owner Username"}},"type":"object","required":["app_id","app_name","role","owner_username"],"title":"InviteSummary"},"JobResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"kind":{"type":"string","title":"Kind"},"subject_user_id":{"type":"string","format":"uuid","title":"Subject User Id"},"source_host_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Source Host Id"},"target_host_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Target Host Id"},"status":{"type":"string","title":"Status"},"current_step":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Current Step"},"params":{"additionalProperties":true,"type":"object","title":"Params"},"journal":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Journal"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"started_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Started At"},"finished_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Finished At"}},"type":"object","required":["id","kind","subject_user_id","source_host_id","target_host_id","status","current_step","params","journal","error","created_at","updated_at","started_at","finished_at"],"title":"JobResponse"},"LatencyHealth":{"properties":{"available":{"type":"boolean","title":"Available"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"},"window_hours":{"type":"integer","title":"Window Hours"},"slow_request_threshold_ms":{"type":"integer","title":"Slow Request Threshold Ms"},"requests":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Requests"},"p50_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"P50 Ms"},"p95_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"P95 Ms"},"p99_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"P99 Ms"},"max_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Ms"},"slow_requests":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Slow Requests"},"slow_request_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Slow Request Pct"},"by_proto":{"items":{"$ref":"#/components/schemas/LatencyProto"},"type":"array","title":"By Proto"}},"type":"object","required":["available","reason","window_hours","slow_request_threshold_ms","requests","p50_ms","p95_ms","p99_ms","max_ms","slow_requests","slow_request_pct","by_proto"],"title":"LatencyHealth","description":"How long this channel took to answer its recent requests.\n\nEvery figure covers the SERVER-SIDE span alone: the edge starts the\nclock when it reads the request, and stops it when it writes the\nlast byte of the reply. The TCP handshake, the accept queue and the\nTLS handshake all end before that clock starts, so a stall while the\nvisitor connects is invisible here. The transfer of the reply is\ninside the span, so a slow visitor link raises the figure. A high\nfigure alone does not prove that the app is slow, and no figure\nstates an end-to-end time.\n\n``p50_ms``, ``p95_ms`` and ``p99_ms`` are upper bounds off the\nhistogram edges, not measured durations: 95 of every 100 requests\nfinished under ``p95_ms``. ``max_ms`` is the one measured figure\namong them, and it is the worst request of the window.\n\n``window_hours`` names the scope of the read, so it stays present\nwhen the block is not available. It states what the route looked at,\nand it measures nothing about the channel.\n\n``slow_request_threshold_ms`` follows that same rule: it states the\nreader's cut and measures nothing, so it also stays present in both\nstates. The cut is STRICTLY ABOVE the figure. The console cannot\nimport ``SLOW_REQUEST_MS``, so the payload carries it and the page\nstates one number with the route."},"LatencyProto":{"properties":{"proto":{"type":"string","title":"Proto"},"requests":{"type":"integer","title":"Requests"},"p95_ms":{"type":"integer","title":"P95 Ms"}},"type":"object","required":["proto","requests","p95_ms"],"title":"LatencyProto","description":"The measured requests of one HTTP version, and their p95."},"ListHostsResponse":{"properties":{"hosts":{"items":{"$ref":"#/components/schemas/HostInfo"},"type":"array","title":"Hosts"}},"type":"object","required":["hosts"],"title":"ListHostsResponse"},"MemberListResponse":{"properties":{"members":{"items":{"$ref":"#/components/schemas/MemberResponse"},"type":"array","title":"Members"}},"type":"object","required":["members"],"title":"MemberListResponse"},"MemberResponse":{"properties":{"user_id":{"type":"string","format":"uuid","title":"User Id"},"username":{"type":"string","title":"Username"},"role":{"type":"string","title":"Role"},"status":{"type":"string","title":"Status","default":"active"}},"type":"object","required":["user_id","username","role"],"title":"MemberResponse"},"MoveDatabasesRequest":{"properties":{"target_instance_id":{"type":"string","format":"uuid","title":"Target Instance Id"}},"type":"object","required":["target_instance_id"],"title":"MoveDatabasesRequest"},"MoveUserRequest":{"properties":{"target_host_id":{"type":"string","format":"uuid","title":"Target Host Id"},"force":{"type":"boolean","title":"Force","default":false}},"type":"object","required":["target_host_id"],"title":"MoveUserRequest"},"NoteResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"body":{"type":"string","title":"Body"},"author_user_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Author User Id"},"author_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Author Label"},"agent_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","category","body","author_user_id","author_label","agent_id","created_at"],"title":"NoteResponse"},"NoteSearchHit":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"body":{"type":"string","title":"Body"},"author_user_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Author User Id"},"author_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Author Label"},"agent_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"votes":{"$ref":"#/components/schemas/VoteSummary"},"thread_number":{"type":"integer","title":"Thread Number"},"thread_subject":{"type":"string","title":"Thread Subject"}},"type":"object","required":["id","category","body","author_user_id","author_label","agent_id","created_at","votes","thread_number","thread_subject"],"title":"NoteSearchHit"},"NoteSearchResponse":{"properties":{"notes":{"items":{"$ref":"#/components/schemas/NoteSearchHit"},"type":"array","title":"Notes"}},"type":"object","required":["notes"],"title":"NoteSearchResponse"},"NoteWithVotes":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"body":{"type":"string","title":"Body"},"author_user_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Author User Id"},"author_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Author Label"},"agent_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"votes":{"$ref":"#/components/schemas/VoteSummary"}},"type":"object","required":["id","category","body","author_user_id","author_label","agent_id","created_at","votes"],"title":"NoteWithVotes"},"PendingDeployResponse":{"properties":{"deploy_id":{"type":"string","format":"uuid","title":"Deploy Id"},"sha":{"type":"string","title":"Sha"},"status":{"type":"string","title":"Status"}},"type":"object","required":["deploy_id","sha","status"],"title":"PendingDeployResponse","description":"The channel's in-flight deploy, if one is queued or running."},"PlanChangeRequest":{"properties":{"tier":{"type":"string","title":"Tier"}},"type":"object","required":["tier"],"title":"PlanChangeRequest"},"PortForwardingRequest":{"properties":{"enabled":{"type":"boolean","title":"Enabled"}},"type":"object","required":["enabled"],"title":"PortForwardingRequest"},"ProjectDeployHistoryItem":{"properties":{"deploy_id":{"type":"string","format":"uuid","title":"Deploy Id"},"git_sha":{"type":"string","title":"Git Sha"},"status":{"type":"string","title":"Status"},"started_at":{"type":"string","format":"date-time","title":"Started At"},"finished_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Finished At"},"channel_id":{"type":"string","format":"uuid","title":"Channel Id"}},"type":"object","required":["deploy_id","git_sha","status","started_at","finished_at","channel_id"],"title":"ProjectDeployHistoryItem"},"ReactivateHostRequest":{"properties":{"force":{"type":"boolean","title":"Force","default":false}},"type":"object","title":"ReactivateHostRequest"},"RebuildResponse":{"properties":{"deploy_id":{"type":"string","format":"uuid","title":"Deploy Id"},"channel_id":{"type":"string","format":"uuid","title":"Channel Id"},"git_sha":{"type":"string","title":"Git Sha"},"status":{"type":"string","title":"Status"}},"type":"object","required":["deploy_id","channel_id","git_sha","status"],"title":"RebuildResponse"},"RegisterResponse":{"properties":{"client_id":{"type":"string","title":"Client Id"},"client_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Name"},"redirect_uris":{"items":{"type":"string"},"type":"array","title":"Redirect Uris"},"token_endpoint_auth_method":{"type":"string","title":"Token Endpoint Auth Method","default":"none"},"grant_types":{"items":{"type":"string"},"type":"array","title":"Grant Types","default":["authorization_code","refresh_token"]},"response_types":{"items":{"type":"string"},"type":"array","title":"Response Types","default":["code"]}},"type":"object","required":["client_id","client_name","redirect_uris"],"title":"RegisterResponse"},"RegistrationResponse":{"properties":{"user_id":{"type":"string","format":"uuid","title":"User Id"},"username":{"type":"string","title":"Username"},"plan":{"type":"string","title":"Plan"},"token":{"type":"string","title":"Token"},"token_expires_at":{"type":"string","format":"date-time","title":"Token Expires At"},"ssh_key_id":{"type":"string","format":"uuid","title":"Ssh Key Id"},"fingerprint_sha256":{"type":"string","title":"Fingerprint Sha256"},"git_ssh_host":{"type":"string","title":"Git Ssh Host"},"limits":{"additionalProperties":true,"type":"object","title":"Limits"},"next":{"additionalProperties":{"type":"string"},"type":"object","title":"Next"}},"type":"object","required":["user_id","username","plan","token","token_expires_at","ssh_key_id","fingerprint_sha256","git_ssh_host","limits","next"],"title":"RegistrationResponse"},"RemoveInstanceRequest":{"properties":{"former_writer_host_id":{"type":"string","format":"uuid","title":"Former Writer Host Id"},"request_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Request Id"}},"type":"object","required":["former_writer_host_id"],"title":"RemoveInstanceRequest"},"RemoveReplicaRequest":{"properties":{"skip_cell":{"type":"boolean","title":"Skip Cell","default":false},"unprotect":{"type":"boolean","title":"Unprotect","default":false},"request_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Request Id"}},"type":"object","title":"RemoveReplicaRequest"},"ResetRequest":{"properties":{"confirm_db_name":{"type":"string","title":"Confirm Db Name"},"request_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Request Id"}},"type":"object","required":["confirm_db_name"],"title":"ResetRequest"},"ResizeInstanceRequest":{"properties":{"profile":{"type":"string","title":"Profile"},"request_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Request Id"},"force":{"type":"boolean","title":"Force","default":false}},"type":"object","required":["profile"],"title":"ResizeInstanceRequest"},"ResourceHealth":{"properties":{"available":{"type":"boolean","title":"Available"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"},"cpu_pct_of_limit":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cpu Pct Of Limit"},"cpu_soft_cores":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cpu Soft Cores"},"mem_peak_bytes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Mem Peak Bytes"},"mem_limit_bytes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Mem Limit Bytes"},"psi_full_avg60_peak":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Psi Full Avg60 Peak"},"io_psi_some_avg60_peak":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Io Psi Some Avg60 Peak"},"oom_kills":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Oom Kills"}},"type":"object","required":["available","reason","cpu_pct_of_limit","cpu_soft_cores","mem_peak_bytes","mem_limit_bytes","psi_full_avg60_peak","io_psi_some_avg60_peak","oom_kills"],"title":"ResourceHealth","description":"Recent CPU, memory, pressure and disk figures of the account."},"RestoreProgress":{"properties":{"phase":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phase"},"bytes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Bytes"},"segments":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Segments"},"waiting":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Waiting"},"items_done":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Items Done"},"items_total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Items Total"}},"type":"object","required":["phase","bytes","segments","waiting","items_done","items_total"],"title":"RestoreProgress","description":"How far a restore's current step got.\n\nWhile the restore extracts, ``phase`` names the extract's phase:\n``fetch``, ``refetch``, ``replay``, or ``dump``. ``bytes`` counts the\nbytes the database server has fetched from the backup, or written to\nthe snapshot's dump, so far, and ``segments`` counts the log files\nthe ``replay`` phase has replayed. ``waiting`` names why the extract\nwaits for the database server, such as ``extraction_busy``.\n``items_done`` of ``items_total`` counts the snapshot's entries\nloaded. A field the step does not report is null."},"RestoreRequest":{"properties":{"confirm_db_name":{"type":"string","title":"Confirm Db Name"},"snapshot_id":{"type":"string","format":"uuid","title":"Snapshot Id"},"request_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Request Id"},"during_restore":{"$ref":"#/components/schemas/DuringRestore","default":"read_only"},"maintenance_channel_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Maintenance Channel Id"}},"type":"object","required":["confirm_db_name","snapshot_id"],"title":"RestoreRequest"},"RestoreStatusResponse":{"properties":{"restore_id":{"type":"string","format":"uuid","title":"Restore Id"},"app_id":{"type":"string","format":"uuid","title":"App Id"},"channel_id":{"type":"string","format":"uuid","title":"Channel Id"},"snapshot_id":{"type":"string","format":"uuid","title":"Snapshot Id"},"during_restore":{"$ref":"#/components/schemas/DuringRestore"},"maintenance_channel_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Maintenance Channel Id"},"status":{"type":"string","enum":["queued","extracting","loading","swapping","succeeded","failed"],"title":"Status"},"waiting":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Waiting"},"queue_ahead":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Queue Ahead"},"activity":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Activity"},"progress":{"anyOf":[{"$ref":"#/components/schemas/RestoreProgress"},{"type":"null"}]},"error_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Code"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"notice":{"type":"string","title":"Notice"},"submitted_at":{"type":"string","format":"date-time","title":"Submitted At"},"started_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Started At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"finished_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Finished At"}},"type":"object","required":["restore_id","app_id","channel_id","snapshot_id","during_restore","maintenance_channel_id","status","waiting","queue_ahead","activity","progress","error_code","message","notice","submitted_at","started_at","updated_at","finished_at"],"title":"RestoreStatusResponse","description":"One restore of a channel's database.\n\n``status`` is ``queued``, ``extracting``, ``loading``, ``swapping``,\n``succeeded``, or ``failed``. ``queued`` also covers a restore that\nhas started and puts the read-only hold on, so a surface shows\n``activity`` and ``waiting`` rather than the word \"queued\".\n``during_restore`` is the mode the request chose, ``read_only``,\n``maintenance``, or ``live``, and ``maintenance_channel_id`` the\nstatic channel a ``maintenance`` restore's page shows, null for the\nplatform's page. While the restore is active, ``waiting`` names why\nit waits, ``queue_ahead`` counts the restores before a ``received``\none on its database server, and ``activity`` says in one plain\nsentence what it does now (``postgres/api.py::restore_queue_state``);\neach is null when it does not apply. ``error_code`` is set on a\nfailed restore, and ``message`` says why in plain words. ``notice``\nis the mode's sentence from :data:`RESTORE_MODE_NOTICES`."},"RestoreSubmitResponse":{"properties":{"restore_id":{"type":"string","format":"uuid","title":"Restore Id"},"app_id":{"type":"string","format":"uuid","title":"App Id"},"channel_id":{"type":"string","format":"uuid","title":"Channel Id"},"snapshot_id":{"type":"string","format":"uuid","title":"Snapshot Id"},"during_restore":{"$ref":"#/components/schemas/DuringRestore"},"maintenance_channel_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Maintenance Channel Id"},"status":{"type":"string","enum":["queued","extracting","loading","swapping","succeeded","failed"],"title":"Status"},"waiting":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Waiting"},"queue_ahead":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Queue Ahead"},"activity":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Activity"},"progress":{"anyOf":[{"$ref":"#/components/schemas/RestoreProgress"},{"type":"null"}]},"error_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Code"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"notice":{"type":"string","title":"Notice"},"submitted_at":{"type":"string","format":"date-time","title":"Submitted At"},"started_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Started At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"finished_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Finished At"},"cancelled_deploys":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Cancelled Deploys"}},"type":"object","required":["restore_id","app_id","channel_id","snapshot_id","during_restore","maintenance_channel_id","status","waiting","queue_ahead","activity","progress","error_code","message","notice","submitted_at","started_at","updated_at","finished_at","cancelled_deploys"],"title":"RestoreSubmitResponse","description":"The answer to a restore's start: its status, and the deploys the\nstart cancelled.\n\n``cancelled_deploys`` names the channel's queued deploys and rewinds\nthat the start turned ``cancelled``, oldest first\n(``deploy/dispatch.py::cancel_queued_for_restore``). A replay of the\nrequest names these, and any row the pickup fail-safe cancelled for\nthe restore (``deploy/dispatch.py::cancelled_for_restore``)."},"ResumeJobRequest":{"properties":{"skip_source_blob_push":{"type":"boolean","title":"Skip Source Blob Push","default":false},"rebuild_images":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Rebuild Images","default":[]}},"type":"object","title":"ResumeJobRequest"},"RuntimeHealth":{"properties":{"available":{"type":"boolean","title":"Available"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"},"container_index":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Container Index"},"container_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Container Id"},"source":{"anyOf":[{"type":"string","enum":["live","archive"]},{"type":"null"}],"title":"Source"},"running":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Running"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"exit_code":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Exit Code"},"oom_killed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Oom Killed"},"restart_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Restart Count"},"started_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Started At"},"finished_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Finished At"}},"type":"object","required":["available","reason","container_index","container_id","source","running","status","exit_code","oom_killed","restart_count","started_at","finished_at"],"title":"RuntimeHealth","description":"Restart and exit facts of the channel's app container.\n\nThe block describes the running container when one exists, else the\nnewest container of the channel, live or archived.\n``container_index``, ``container_id``, and ``source`` name that\ncontainer. ``started_at`` is docker's last start of it, which a\nrestart-policy restart moves forward; ``restart_count`` counts those\nrestarts."},"RuntimeLogRequest":{"properties":{"container_index":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Container Index"},"command":{"anyOf":[{"type":"string","maxLength":4096},{"type":"null"}],"title":"Command"}},"type":"object","title":"RuntimeLogRequest","description":"One runtime-log read.\n\n``command`` is a shell command run against the log inside a\ndisposable sandbox on the cell; omitting it returns the status facts\nalone. There is deliberately no server-side default command — an\nimplicit one would be exactly the magic this surface removes."},"RuntimeLogResponse":{"properties":{"container_index":{"type":"integer","title":"Container Index"},"container_id":{"type":"string","title":"Container Id"},"container_name":{"type":"string","title":"Container Name"},"running":{"type":"boolean","title":"Running"},"source":{"type":"string","enum":["live","archive"],"title":"Source"},"status":{"type":"string","title":"Status"},"exit_code":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Exit Code"},"oom_killed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Oom Killed"},"restart_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Restart Count"},"started_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Started At"},"finished_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Finished At"},"available_indices":{"items":{"type":"integer"},"type":"array","title":"Available Indices"},"running_index":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Running Index"},"log_bytes":{"type":"integer","title":"Log Bytes"},"output":{"type":"string","title":"Output"},"command_exit_code":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Command Exit Code"},"timed_out":{"type":"boolean","title":"Timed Out"},"truncated":{"type":"boolean","title":"Truncated"}},"type":"object","required":["container_index","container_id","container_name","running","source","status","exit_code","oom_killed","restart_count","started_at","finished_at","available_indices","running_index","log_bytes","output","command_exit_code","timed_out","truncated"],"title":"RuntimeLogResponse","description":"Status facts + the command's own output.\n\n``exit_code`` is the LOGGED CONTAINER's; ``command_exit_code`` is\nthe query command's (``None`` when no command ran, or when the\nsandbox was killed at the timeout). ``running_index`` is the index\nof the running container, whatever index this reply shows, and is\n``None`` when nothing runs."},"SetRoleRequest":{"properties":{"role":{"type":"string","enum":["admin","member"],"title":"Role"}},"type":"object","required":["role"],"title":"SetRoleRequest"},"SnapshotDownloadTokenResponse":{"properties":{"token":{"type":"string","title":"Token"},"download_url":{"type":"string","title":"Download Url"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At"}},"type":"object","required":["token","download_url","expires_at"],"title":"SnapshotDownloadTokenResponse"},"SnapshotListItem":{"properties":{"snapshot_id":{"type":"string","format":"uuid","title":"Snapshot Id"},"deploy_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Deploy Id"},"kind":{"type":"string","title":"Kind"},"git_sha":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Git Sha"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"aligned_blob":{"type":"boolean","title":"Aligned Blob"},"recoverable":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Recoverable"},"restores":{"items":{"$ref":"#/components/schemas/SnapshotRestoreItem"},"type":"array","title":"Restores"}},"type":"object","required":["snapshot_id","deploy_id","kind","git_sha","created_at","aligned_blob","recoverable","restores"],"title":"SnapshotListItem","description":"One snapshot of a channel's Postgres database.\n\n``kind`` is ``pre_deploy`` or ``nightly`` and tells the user which\nretention rule holds the row. ``deploy_id`` is null on a ``nightly``\nrow, because no deploy triggers one. ``git_sha`` is the code of the\nsame checkpoint, so a caller can ask the code download for the leg\nthat matches this one. It is null on a row from before the column.\n\n``aligned_blob`` is true when a ``ChannelBlobSnapshot`` of the same\nchannel names this row — the two legs of one checkpoint.\n``recoverable`` reports whether a restore can reach this snapshot: it\nis true while the marker's instant sits inside its instance's WAL-G\nwindow, and null (unknown) until that window has an upper bound. It\nis false outside the window, or when the marker names no instance.\n\n``restores`` lists the restores from this snapshot, newest first: the\nactive one and those of the last 30 days, which the command ledger\nkeeps (``postgres/api.py::restores_by_snapshot``). An empty list\nmeans that no restore named the snapshot in that time."},"SnapshotRestoreItem":{"properties":{"restore_id":{"type":"string","format":"uuid","title":"Restore Id"},"status":{"type":"string","enum":["queued","extracting","loading","swapping","succeeded","failed"],"title":"Status"},"during_restore":{"$ref":"#/components/schemas/DuringRestore"},"started_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Started At"},"swapped_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Swapped At"},"finished_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Finished At"}},"type":"object","required":["restore_id","status","during_restore","started_at","swapped_at","finished_at"],"title":"SnapshotRestoreItem","description":"One restore from a snapshot, as the snapshot list carries it.\n\n``status`` and ``during_restore`` read as on the restore's status.\n``swapped_at`` is when the restored database replaced the channel's\ndatabase: the end of a ``succeeded`` restore, and null for any other."},"SshKeyInfo":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"algo":{"type":"string","title":"Algo"},"fingerprint":{"type":"string","title":"Fingerprint"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"last_used_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Used At"},"api_login":{"type":"boolean","title":"Api Login"}},"type":"object","required":["id","label","algo","fingerprint","created_at","last_used_at","api_login"],"title":"SshKeyInfo"},"SshKeyListResponse":{"properties":{"ssh_keys":{"items":{"$ref":"#/components/schemas/SshKeyInfo"},"type":"array","title":"Ssh Keys"}},"type":"object","required":["ssh_keys"],"title":"SshKeyListResponse"},"SshKeyLoginRequest":{"properties":{"public_key":{"type":"string","title":"Public Key"},"timestamp":{"type":"integer","title":"Timestamp"},"signature":{"type":"string","title":"Signature"}},"additionalProperties":false,"type":"object","required":["public_key","timestamp","signature"],"title":"SshKeyLoginRequest"},"SshKeyLoginResponse":{"properties":{"token":{"type":"string","title":"Token"},"token_expires_at":{"type":"string","format":"date-time","title":"Token Expires At"},"user_id":{"type":"string","format":"uuid","title":"User Id"},"username":{"type":"string","title":"Username"}},"type":"object","required":["token","token_expires_at","user_id","username"],"title":"SshKeyLoginResponse"},"StatementsHealth":{"properties":{"available":{"type":"boolean","title":"Available"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"},"statements":{"items":{"$ref":"#/components/schemas/TopStatement"},"type":"array","title":"Statements"}},"type":"object","required":["available","reason","statements"],"title":"StatementsHealth","description":"The slowest statements of the channel database."},"StatusResponse":{"properties":{"connected":{"type":"boolean","title":"Connected"},"remote_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Remote Url"},"public_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Public Key"},"connected_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Connected At"},"last_synced_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Synced At"},"last_sync_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Sync Status"},"last_sync_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Sync Error"},"last_sync_refs":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Last Sync Refs"},"default_branch":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Default Branch"},"notify_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notify Url"},"last_ping_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Ping At"}},"type":"object","required":["connected"],"title":"StatusResponse"},"SubmitAppFeedbackRequest":{"properties":{"owner_username":{"type":"string","title":"Owner Username"},"app_name":{"type":"string","title":"App Name"},"body":{"type":"string","title":"Body"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"}},"type":"object","required":["owner_username","app_name","body"],"title":"SubmitAppFeedbackRequest"},"SubmitFeedbackRequest":{"properties":{"message":{"type":"string","title":"Message"},"subject":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject"},"app_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"App Id"}},"type":"object","required":["message"],"title":"SubmitFeedbackRequest"},"SubmitFeedbackResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"status":{"type":"string","title":"Status"}},"type":"object","required":["id","status"],"title":"SubmitFeedbackResponse"},"ThreadDetailResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"number":{"type":"integer","title":"Number"},"subject":{"type":"string","title":"Subject"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"channel_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Channel Id"},"branch":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Branch"},"sha":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sha"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"notes":{"items":{"$ref":"#/components/schemas/NoteWithVotes"},"type":"array","title":"Notes"}},"type":"object","required":["id","number","subject","category","channel_id","branch","sha","created_at","notes"],"title":"ThreadDetailResponse"},"ThreadListResponse":{"properties":{"threads":{"items":{"$ref":"#/components/schemas/ThreadSummary"},"type":"array","title":"Threads"}},"type":"object","required":["threads"],"title":"ThreadListResponse"},"ThreadSummary":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"number":{"type":"integer","title":"Number"},"subject":{"type":"string","title":"Subject"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"channel_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Channel Id"},"branch":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Branch"},"sha":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sha"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"note_count":{"type":"integer","title":"Note Count"},"last_note_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Note At"}},"type":"object","required":["id","number","subject","category","channel_id","branch","sha","created_at","note_count","last_note_at"],"title":"ThreadSummary"},"TopStatement":{"properties":{"query":{"type":"string","title":"Query"},"truncated":{"type":"boolean","title":"Truncated"},"calls":{"type":"integer","title":"Calls"},"total_exec_ms":{"type":"number","title":"Total Exec Ms"},"mean_exec_ms":{"type":"number","title":"Mean Exec Ms"},"rows":{"type":"integer","title":"Rows"}},"type":"object","required":["query","truncated","calls","total_exec_ms","mean_exec_ms","rows"],"title":"TopStatement","description":"One statement of the channel database, with its cost."},"TransferListResponse":{"properties":{"transfers":{"items":{"$ref":"#/components/schemas/TransferSummary"},"type":"array","title":"Transfers"}},"type":"object","required":["transfers"],"title":"TransferListResponse"},"TransferOfferResponse":{"properties":{"transfer_id":{"type":"string","format":"uuid","title":"Transfer Id"},"to_username":{"type":"string","title":"To Username"},"status":{"type":"string","title":"Status"},"recipient_has_headroom":{"type":"boolean","title":"Recipient Has Headroom"}},"type":"object","required":["transfer_id","to_username","status","recipient_has_headroom"],"title":"TransferOfferResponse"},"TransferRequest":{"properties":{"username_or_email":{"type":"string","title":"Username Or Email"}},"type":"object","required":["username_or_email"],"title":"TransferRequest"},"TransferSummary":{"properties":{"transfer_id":{"type":"string","format":"uuid","title":"Transfer Id"},"app_id":{"type":"string","format":"uuid","title":"App Id"},"app_name":{"type":"string","title":"App Name"},"from_username":{"type":"string","title":"From Username"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["transfer_id","app_id","app_name","from_username","created_at"],"title":"TransferSummary"},"TreeFileEntry":{"properties":{"path":{"type":"string","title":"Path"},"kind":{"type":"string","title":"Kind"},"size":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Size"}},"type":"object","required":["path","kind","size"],"title":"TreeFileEntry"},"TreeResponse":{"properties":{"ref":{"type":"string","title":"Ref"},"sha":{"type":"string","title":"Sha"},"files":{"items":{"$ref":"#/components/schemas/TreeFileEntry"},"type":"array","title":"Files"}},"type":"object","required":["ref","sha","files"],"title":"TreeResponse"},"UpdateChannelRequest":{"properties":{"git_ref_binding":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Git Ref Binding"},"follows_branch":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Follows Branch"}},"type":"object","title":"UpdateChannelRequest","description":"The fields ``PATCH`` can change. An omitted field stays as it is.\n\nAn explicit ``null`` is refused for both: a channel cannot be unbound,\nand following is on or off."},"UpdateHostRequest":{"properties":{"git_endpoint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Git Endpoint"},"machine_class":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Machine Class"}},"additionalProperties":false,"type":"object","title":"UpdateHostRequest"},"UpsertEnvRequest":{"properties":{"key":{"type":"string","title":"Key"},"value":{"type":"string","title":"Value"},"kind":{"anyOf":[{"type":"string","enum":["env","secret"]},{"type":"null"}],"title":"Kind"},"channel_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Channel Id"}},"type":"object","required":["key","value"],"title":"UpsertEnvRequest"},"UserBlobStorage":{"properties":{"blob_usage_bytes":{"type":"integer","title":"Blob Usage Bytes"},"store_count":{"type":"integer","title":"Store Count"}},"type":"object","required":["blob_usage_bytes","store_count"],"title":"UserBlobStorage","description":"User-level object-store (blob) storage rollup."},"UserPostgresStorage":{"properties":{"database_size_bytes":{"type":"integer","title":"Database Size Bytes"},"database_count":{"type":"integer","title":"Database Count"}},"type":"object","required":["database_size_bytes","database_count"],"title":"UserPostgresStorage"},"UserUsage":{"properties":{"plan":{"type":"string","title":"Plan"},"storage_bytes":{"type":"integer","title":"Storage Bytes"},"storage_limit_mb":{"type":"integer","title":"Storage Limit Mb"},"bandwidth_month_bytes":{"type":"integer","title":"Bandwidth Month Bytes"}},"type":"object","required":["plan","storage_bytes","storage_limit_mb","bandwidth_month_bytes"],"title":"UserUsage","description":"Per-account database storage and egress.\n\nStorage is display / soft-cap only: over-limit warns on the account\npage, it never blocks. Its limit is resolved server-side from\n``plans.py`` so the web layer never imports the plan table. Egress\ncarries NO limit field, because no plan states an egress figure:\nthe platform reports the month's measured bytes and nothing else."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"VerificationRequest":{"properties":{"email":{"type":"string","title":"Email"}},"additionalProperties":false,"type":"object","required":["email"],"title":"VerificationRequest"},"VerificationResponse":{"properties":{"status":{"type":"string","const":"sent","title":"Status"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"}},"type":"object","required":["status","expires_at"],"title":"VerificationResponse"},"VerificationStatus":{"properties":{"status":{"type":"string","enum":["none","pending","expired","verified","conflict"],"title":"Status"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At"},"plan":{"type":"string","title":"Plan"}},"type":"object","required":["status","expires_at","plan"],"title":"VerificationStatus"},"VoteSummary":{"properties":{"up":{"type":"integer","title":"Up"},"down":{"type":"integer","title":"Down"},"member_count":{"type":"integer","title":"Member Count"}},"type":"object","required":["up","down","member_count"],"title":"VoteSummary"},"xhostd__routes__oauth__RegisterRequest":{"properties":{"redirect_uris":{"items":{"type":"string"},"type":"array","title":"Redirect Uris"},"client_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Name"}},"type":"object","required":["redirect_uris"],"title":"RegisterRequest"},"xhostd__routes__registration__RegisterRequest":{"properties":{"public_key":{"type":"string","title":"Public Key"},"timestamp":{"type":"integer","title":"Timestamp"},"signature":{"type":"string","title":"Signature"},"username":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Username"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"}},"additionalProperties":false,"type":"object","required":["public_key","timestamp","signature"],"title":"RegisterRequest"}}}}