{"openapi":"3.1.0","info":{"title":"slopner API","version":"1","description":"Everything the slopner console does: projects, sandboxes, agents and their conversations, tasks, goals, the library, previews and domains.\n\n**Authentication:** create a personal API key in the console (Account → API keys) and send it as `Authorization: Bearer slp_…`. A key acts as its owner: the same projects and permissions. Read-only keys can only make GET requests.\n\n**Errors** are JSON `{ \"error\": \"…\" }` with a 4xx/5xx status. **Rate limit:** 600 requests per minute per key (429 beyond).\n\nGuides and examples: https://www.slopner.lol/#/docs/api"},"tags":[{"name":"Account","description":"Who you are, and your API keys."},{"name":"Projects","description":"A project connects slopner to one Hetzner Cloud project; everything else belongs to one."},{"name":"Sandboxes","description":"Hetzner servers your agents work on: create, sleep and wake, power, firewall."},{"name":"Domains & previews","description":"Private HTTPS previews of web services, sharing them, and your own domains on ports."},{"name":"Agents","description":"AI agents: create them, talk to them, run commands, answer what they ask."},{"name":"Tasks","description":"Work with acceptance criteria, an assignee and an optional reviewer agent."},{"name":"Library","description":"Files shared with every agent of a project (on your Object Storage)."},{"name":"Secrets","description":"API keys and tokens for agents; values are write-only."},{"name":"Inbox & activity","description":"What waits for your approval, what happened, and where you get notified."},{"name":"Administration","description":"Accounts, invites and feedback."}],"paths":{"/me":{"get":{"tags":["Account"],"summary":"The account behind this key or session","operationId":"getMe","parameters":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserRecord"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api-keys":{"get":{"tags":["Account"],"summary":"List your API keys","description":"Needs a signed-in console session; API keys can't manage API keys.","operationId":"getApiKeys","parameters":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ApiKeyRecord"}}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"session":[]}]},"post":{"tags":["Account"],"summary":"Create an API key (shown once)","description":"Needs a signed-in console session; API keys can't manage API keys.","operationId":"postApiKeys","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreate"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string"},"apiKey":{"$ref":"#/components/schemas/ApiKeyRecord"}},"required":["key","apiKey"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"session":[]}]}},"/api-keys/{id}":{"delete":{"tags":["Account"],"summary":"Revoke an API key","description":"Needs a signed-in console session; API keys can't manage API keys.","operationId":"deleteApiKeysById","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true}},"required":["ok"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"session":[]}]}},"/projects":{"get":{"tags":["Projects"],"summary":"Projects you belong to (admins: all)","operationId":"getProjects","parameters":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ProjectRecord"}}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Projects"],"summary":"Create a project (connects a Hetzner Cloud project)","operationId":"postProjects","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectCreate"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectRecord"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/inference/models":{"post":{"tags":["Projects"],"summary":"Models an inference key can use (checks the key; to pick a project's default model)","operationId":"postInferenceModels","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"provider":{"$ref":"#/components/schemas/InferenceProvider"},"key":{"type":"string"}},"required":["provider","key"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ModelOption"}}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/projects/{id}":{"patch":{"tags":["Projects"],"summary":"Rename a project or replace its credentials (owners)","operationId":"patchProjectsById","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectUpdate"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectRecord"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Projects"],"summary":"Delete a project (its sandboxes must be deleted first)","operationId":"deleteProjectsById","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true}},"required":["ok"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/projects/{id}/sandbox-options":{"get":{"tags":["Projects"],"summary":"Sandbox sizes and regions available","operationId":"getProjectsByIdSandboxOptions","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SandboxOptions"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/projects/{id}/models":{"get":{"tags":["Projects"],"summary":"Models of the project's inference provider","operationId":"getProjectsByIdModels","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ModelOption"}}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/projects/{id}/dns-zones":{"get":{"tags":["Projects"],"summary":"DNS zones in the project's Hetzner DNS","operationId":"getProjectsByIdDnsZones","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}}}}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/projects/{id}/members":{"get":{"tags":["Projects"],"summary":"Members of a project","operationId":"getProjectsByIdMembers","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ProjectMember"}}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Projects"],"summary":"Add an existing account to a project (owners)","operationId":"postProjectsByIdMembers","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"username":{"type":"string"},"role":{"enum":["owner","member"]}},"required":["username"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectMember"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/projects/{id}/members/{userId}":{"patch":{"tags":["Projects"],"summary":"Change a member's role (owners)","operationId":"patchProjectsByIdMembersByUserId","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"userId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"role":{"enum":["owner","member"]}},"required":["role"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectMember"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Projects"],"summary":"Remove a member (or leave)","operationId":"deleteProjectsByIdMembersByUserId","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"userId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true}},"required":["ok"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/sandboxes":{"get":{"tags":["Sandboxes"],"summary":"Sandboxes of a project","operationId":"getSandboxes","parameters":[{"in":"query","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project id"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SandboxRecord"}}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Sandboxes"],"summary":"Create a sandbox (a Hetzner server, billed by Hetzner)","operationId":"postSandboxes","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SandboxCreate"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SandboxRecord"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/sandboxes/{id}":{"get":{"tags":["Sandboxes"],"summary":"A sandbox","operationId":"getSandboxesById","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SandboxRecord"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Sandboxes"],"summary":"Rename it or set when it sleeps","operationId":"patchSandboxesById","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SandboxUpdate"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SandboxRecord"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Sandboxes"],"summary":"Delete a sandbox and the agents on it","operationId":"deleteSandboxesById","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true}},"required":["ok"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/sandboxes/{id}/sleep":{"post":{"tags":["Sandboxes"],"summary":"Put it to sleep (snapshot, delete the server); runs in the background","operationId":"postSandboxesByIdSleep","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true}},"required":["ok"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/sandboxes/{id}/wake":{"post":{"tags":["Sandboxes"],"summary":"Wake it from its snapshot (about a minute)","operationId":"postSandboxesByIdWake","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SandboxRecord"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/sandboxes/{id}/keep":{"post":{"tags":["Sandboxes"],"summary":"Make a one-shot sandbox a normal one","operationId":"postSandboxesByIdKeep","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SandboxRecord"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/sandboxes/{id}/power":{"post":{"tags":["Sandboxes"],"summary":"Reboot, power off or power on","operationId":"postSandboxesByIdPower","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"action":{"enum":["reboot","poweroff","poweron"]}},"required":["action"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true}},"required":["ok"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/sandboxes/{id}/firewall/rules":{"post":{"tags":["Sandboxes"],"summary":"Open a port to the internet (firewall rule)","operationId":"postSandboxesByIdFirewallRules","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"port":{"type":["string","number"]},"protocol":{"enum":["tcp","udp"]},"sources":{"type":"array","items":{"type":"string"}},"description":{"type":"string"}},"required":["port"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SandboxRecord"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/sandboxes/{id}/firewall/rules/{protocol}/{port}":{"delete":{"tags":["Sandboxes"],"summary":"Close a port to the internet","operationId":"deleteSandboxesByIdFirewallRulesByProtocolByPort","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"protocol","in":"path","required":true,"schema":{"type":"string"}},{"name":"port","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SandboxRecord"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/sandboxes/{id}/expose":{"post":{"tags":["Sandboxes"],"summary":"Open a port to the internet, optionally with a DNS name","operationId":"postSandboxesByIdExpose","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortExpose"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"sandbox":{"$ref":"#/components/schemas/SandboxRecord"},"hostname":{"type":["string","null"]},"url":{"type":"string"}}}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/sandboxes/{id}/unexpose":{"post":{"tags":["Sandboxes"],"summary":"Close a port again (and remove the DNS records made with it)","operationId":"postSandboxesByIdUnexpose","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"port":{"type":["string","number"]},"protocol":{"type":"string"},"removeDns":{"type":"boolean"}},"required":["port"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SandboxRecord"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/sandboxes/{id}/domains":{"get":{"tags":["Domains & previews"],"summary":"Custom domains of a sandbox","operationId":"getSandboxesByIdDomains","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/DomainRecord"}}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Domains & previews"],"summary":"Attach a domain to a port (HTTPS via Caddy on the sandbox)","operationId":"postSandboxesByIdDomains","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainAttach"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainRecord"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/sandboxes/{id}/domains/{domain}":{"delete":{"tags":["Domains & previews"],"summary":"Detach a domain","operationId":"deleteSandboxesByIdDomainsByDomain","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"domain","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true}},"required":["ok"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/sandboxes/{id}/domains/{domain}/check":{"post":{"tags":["Domains & previews"],"summary":"Check its DNS and HTTPS","operationId":"postSandboxesByIdDomainsByDomainCheck","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"domain","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainRecord"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/sandboxes/{id}/domain-zone":{"get":{"tags":["Domains & previews"],"summary":"Is a domain in the project's Hetzner DNS? Which records it needs","operationId":"getSandboxesByIdDomainZone","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"in":"query","name":"domain","required":true,"schema":{"type":"string"},"description":"e.g. app.example.com"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"zone":{"type":["string","null"]},"expected":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"value":{"type":"string"}}}}}}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/sandboxes/{id}/previews/{port}":{"get":{"tags":["Domains & previews"],"summary":"Who can open a port's preview","operationId":"getSandboxesByIdPreviewsByPort","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"port","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreviewAccess"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"tags":["Domains & previews"],"summary":"Set who can open the preview","operationId":"putSandboxesByIdPreviewsByPort","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"port","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"mode":{"$ref":"#/components/schemas/PreviewVisibility"},"usernames":{"type":"array","items":{"type":"string"}}},"required":["mode"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreviewAccess"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/sandboxes/{id}/previews/{port}/rotate":{"post":{"tags":["Domains & previews"],"summary":"New share link (the old one stops working)","operationId":"postSandboxesByIdPreviewsByPortRotate","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"port","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreviewAccess"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/previews":{"post":{"tags":["Domains & previews"],"summary":"A signed preview URL to open in a browser","operationId":"postPreviews","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"sandboxId":{"type":"string"},"port":{"type":"number"},"path":{"type":"string"}},"required":["sandboxId","port"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string"}},"required":["url"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/ports":{"get":{"tags":["Domains & previews"],"summary":"Ports listening on a project's sandboxes","operationId":"getPorts","parameters":[{"in":"query","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project id"},{"in":"query","name":"sandboxId","required":false,"schema":{"type":"string"},"description":"Only this sandbox"},{"in":"query","name":"all","required":false,"schema":{"type":"string"},"description":"1 = include system listeners"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PortOverviewItem"}}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/agents":{"get":{"tags":["Agents"],"summary":"Agents of a project","operationId":"getAgents","parameters":[{"in":"query","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project id"},{"in":"query","name":"sandboxId","required":false,"schema":{"type":"string"},"description":"Only agents on this sandbox"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AgentRecord"}}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Agents"],"summary":"Create an agent (on an existing sandbox or a new one)","operationId":"postAgents","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentCreate"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentRecord"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/agents/{id}":{"get":{"tags":["Agents"],"summary":"An agent","operationId":"getAgentsById","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentRecord"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Agents"],"summary":"Change name, model, autonomy, instructions, secrets…","operationId":"patchAgentsById","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentUpdate"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentRecord"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Agents"],"summary":"Delete an agent and its conversation","operationId":"deleteAgentsById","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"in":"query","name":"helpers","required":false,"schema":{"type":"string"},"description":"1 = also delete the helpers it created"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true}},"required":["ok"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/agents/{id}/messages":{"get":{"tags":["Agents"],"summary":"The conversation","operationId":"getAgentsByIdMessages","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ChatMessage"}}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Agents"],"summary":"Send the agent a message","description":"Delivered after the agent's current step; queued (and the sandbox woken) if it isn't ready. Read the reply with GET /agents/{id}/messages, or poll GET /agents/{id} until its state is idle.","operationId":"postAgentsByIdMessages","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string"},"attachments":{"type":"array","items":{"$ref":"#/components/schemas/Attachment"}}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"queued":{"type":"boolean"},"message":{"anyOf":[{"$ref":"#/components/schemas/ChatMessage"},{"type":"null"}]}}}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/agents/{id}/commands":{"get":{"tags":["Agents"],"summary":"Slash commands the agent understands","operationId":"getAgentsByIdCommands","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CommandInfo"}}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Agents"],"summary":"Run a command (/goal, /loop, /model, /compact…)","operationId":"postAgentsByIdCommands","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"args":{"type":"string"},"tz":{"type":"string","description":"IANA time zone for /loop schedules"}},"required":["name"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"output":{"type":["string","null"]}}}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/agents/{id}/interrupt":{"post":{"tags":["Agents"],"summary":"Stop the current step","operationId":"postAgentsByIdInterrupt","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true}},"required":["ok"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/agents/{id}/stop":{"post":{"tags":["Agents"],"summary":"Stop the agent (its next message starts it again)","operationId":"postAgentsByIdStop","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true}},"required":["ok"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/agents/{id}/approvals/{requestId}":{"post":{"tags":["Agents"],"summary":"Allow or deny something the agent asked for","operationId":"postAgentsByIdApprovalsByRequestId","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"requestId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"allow":{"type":"boolean"},"message":{"type":"string"}},"required":["allow"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true}},"required":["ok"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/agents/{id}/goals":{"get":{"tags":["Agents"],"summary":"Goal history (journal of every turn)","operationId":"getAgentsByIdGoals","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/GoalJournal"}}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/tasks":{"get":{"tags":["Tasks"],"summary":"Tasks of a project","operationId":"getTasks","parameters":[{"in":"query","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project id"},{"in":"query","name":"agentId","required":false,"schema":{"type":"string"},"description":"Only tasks this agent works on or reviews"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/TaskRecord"}}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Tasks"],"summary":"Create a task","operationId":"postTasks","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskCreate"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskRecord"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/tasks/{id}":{"get":{"tags":["Tasks"],"summary":"A task with its events","operationId":"getTasksById","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskRecord"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Tasks"],"summary":"Edit a task","operationId":"patchTasksById","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskUpdate"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskRecord"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Tasks"],"summary":"Delete a task","operationId":"deleteTasksById","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true}},"required":["ok"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/tasks/{id}/start":{"post":{"tags":["Tasks"],"summary":"Hand it to its assignee","operationId":"postTasksByIdStart","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskRecord"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/tasks/{id}/review":{"post":{"tags":["Tasks"],"summary":"Approve or send back submitted work","operationId":"postTasksByIdReview","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"approve":{"type":"boolean"},"notes":{"type":"string"}},"required":["approve"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskRecord"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/tasks/{id}/comment":{"post":{"tags":["Tasks"],"summary":"Comment (answers a blocked task)","operationId":"postTasksByIdComment","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string"}},"required":["text"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskRecord"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/tasks/{id}/cancel":{"post":{"tags":["Tasks"],"summary":"Cancel","operationId":"postTasksByIdCancel","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskRecord"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/tasks/{id}/reopen":{"post":{"tags":["Tasks"],"summary":"Reopen","operationId":"postTasksByIdReopen","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"notes":{"type":"string"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskRecord"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/projects/{id}/library":{"get":{"tags":["Library"],"summary":"Files in the project library","operationId":"getProjectsByIdLibrary","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LibraryInfo"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/projects/{id}/library/setup":{"post":{"tags":["Library"],"summary":"Set up the library (creates an Object Storage bucket, billed by Hetzner)","operationId":"postProjectsByIdLibrarySetup","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LibrarySetup"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LibraryInfo"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/projects/{id}/library/files":{"put":{"tags":["Library"],"summary":"Upload a file (raw body, up to 100 MB)","operationId":"putProjectsByIdLibraryFiles","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"in":"query","name":"path","required":true,"schema":{"type":"string"},"description":"Library path, e.g. briefs/q4.pdf"}],"requestBody":{"required":true,"content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LibraryFile"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Library"],"summary":"Rename/move, describe or pin a file","operationId":"patchProjectsByIdLibraryFiles","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"in":"query","name":"path","required":true,"schema":{"type":"string"},"description":"Current path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LibraryUpdate"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LibraryFile"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Library"],"summary":"Delete a file or folder","operationId":"deleteProjectsByIdLibraryFiles","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"in":"query","name":"path","required":true,"schema":{"type":"string"},"description":"Path"},{"in":"query","name":"folder","required":false,"schema":{"type":"string"},"description":"1 = delete everything under path/"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"deleted":{"type":"number"}}}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/projects/{id}/library/download":{"get":{"tags":["Library"],"summary":"Download (redirects to a short-lived signed URL)","operationId":"getProjectsByIdLibraryDownload","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"in":"query","name":"path","required":true,"schema":{"type":"string"},"description":"Path"},{"in":"query","name":"inline","required":false,"schema":{"type":"string"},"description":"1 = for viewing in a browser"}],"responses":{"302":{"description":"Redirect to a signed download URL"},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/projects/{id}/library/text":{"get":{"tags":["Library"],"summary":"The first 256 KB of a text file","operationId":"getProjectsByIdLibraryText","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"in":"query","name":"path","required":true,"schema":{"type":"string"},"description":"Path"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string"},"truncated":{"type":"boolean"}}}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/secrets":{"get":{"tags":["Secrets"],"summary":"Secrets of a project (values are never returned)","operationId":"getSecrets","parameters":[{"in":"query","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project id"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SecretRecord"}}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Secrets"],"summary":"Store a secret","operationId":"postSecrets","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SecretCreate"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SecretRecord"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/secrets/{id}":{"patch":{"tags":["Secrets"],"summary":"Change value, description or attached agents","operationId":"patchSecretsById","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SecretUpdate"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SecretRecord"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Secrets"],"summary":"Delete a secret","operationId":"deleteSecretsById","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true}},"required":["ok"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/inbox":{"get":{"tags":["Inbox & activity"],"summary":"What waits for your approval","operationId":"getInbox","parameters":[{"in":"query","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project id"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/InboxItem"}}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/activity":{"get":{"tags":["Inbox & activity"],"summary":"The activity log, newest first","operationId":"getActivity","parameters":[{"in":"query","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project id"},{"in":"query","name":"agentId","required":false,"schema":{"type":"string"},"description":"Only this agent"},{"in":"query","name":"sandboxId","required":false,"schema":{"type":"string"},"description":"Only this sandbox"},{"in":"query","name":"limit","required":false,"schema":{"type":"number"},"description":"Max items (default 100)"},{"in":"query","name":"before","required":false,"schema":{"type":"number"},"description":"Items older than this id"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ActivityItem"}}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/notifications":{"get":{"tags":["Inbox & activity"],"summary":"Notification channels of a project","operationId":"getNotifications","parameters":[{"in":"query","name":"projectId","required":true,"schema":{"type":"string"},"description":"Project id"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/NotificationChannel"}}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Inbox & activity"],"summary":"Add a channel (Telegram, Discord, Slack, email, webhook)","operationId":"postNotifications","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationChannelCreate"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationChannel"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/notifications/{id}":{"patch":{"tags":["Inbox & activity"],"summary":"Edit a channel","operationId":"patchNotificationsById","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationChannelUpdate"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationChannel"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Inbox & activity"],"summary":"Delete a channel","operationId":"deleteNotificationsById","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true}},"required":["ok"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/notifications/{id}/test":{"post":{"tags":["Inbox & activity"],"summary":"Send a test notification","operationId":"postNotificationsByIdTest","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true}},"required":["ok"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/notifications/telegram-chats":{"post":{"tags":["Inbox & activity"],"summary":"Chats a Telegram bot can post to (people who messaged it, groups it's in): to pick the chat id when adding a Telegram channel","operationId":"postNotificationsTelegramChats","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"botToken":{"type":"string"},"channelId":{"type":"string","description":"Instead of botToken: the bot of an existing channel"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"title":{"type":"string"}},"required":["id","type","title"]}}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/users":{"get":{"tags":["Administration"],"summary":"All accounts","description":"Admins only.","operationId":"getUsers","parameters":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UserRecord"}}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/users/{id}":{"patch":{"tags":["Administration"],"summary":"Change role or disable","description":"Admins only.","operationId":"patchUsersById","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"role":{"$ref":"#/components/schemas/UserRole"},"disabled":{"type":"boolean"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserRecord"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Administration"],"summary":"Delete an account","description":"Admins only.","operationId":"deleteUsersById","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true}},"required":["ok"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/invites":{"get":{"tags":["Administration"],"summary":"Invites (admins: all; sponsors: their own; owners: with projectId)","operationId":"getInvites","parameters":[{"in":"query","name":"projectId","required":false,"schema":{"type":"string"},"description":"Invites into this project (owners)"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/InviteRecord"}}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Administration"],"summary":"Create an invite (the code is shown once)","operationId":"postInvites","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InviteCreate"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"invite":{"$ref":"#/components/schemas/InviteRecord"},"code":{"type":"string"}},"required":["invite","code"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/invites/{id}":{"delete":{"tags":["Administration"],"summary":"Revoke an invite","operationId":"deleteInvitesById","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true}},"required":["ok"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/feedback":{"post":{"tags":["Administration"],"summary":"Send feedback to the admins","operationId":"postFeedback","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedbackCreate"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedbackRecord"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"tags":["Administration"],"summary":"Feedback","description":"Admins only.","operationId":"getFeedback","parameters":[{"in":"query","name":"status","required":false,"schema":{"type":"string"},"description":"new | forwarded | dismissed"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/FeedbackRecord"}}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/feedback/{id}/files/{fileId}":{"get":{"tags":["Administration"],"summary":"A file attached to feedback (screenshot, video…)","description":"Admins only.","operationId":"getFeedbackByIdFilesByFileId","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"fileId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"string","format":"binary"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/feedback/{id}/forward":{"post":{"tags":["Administration"],"summary":"Forward to the developers (the feedback inbox)","description":"Admins only.","operationId":"postFeedbackByIdForward","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedbackRecord"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/feedback/{id}/dismiss":{"post":{"tags":["Administration"],"summary":"Dismiss","description":"Admins only.","operationId":"postFeedbackByIdDismiss","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedbackRecord"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/feedback/{id}/reopen":{"post":{"tags":["Administration"],"summary":"Back to new","description":"Admins only.","operationId":"postFeedbackByIdReopen","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedbackRecord"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"A personal API key (slp_…) from Account → API keys"},"session":{"type":"apiKey","in":"cookie","name":"slopner_session","description":"The console's sign-in session"}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]},"UserRecord":{"properties":{"createdAt":{"type":"string"},"disabled":{"type":"boolean"},"id":{"type":"string"},"lastSeenAt":{"type":["string","null"]},"projectIds":{"description":"Projects the user belongs to (ids).","items":{"type":"string"},"type":"array"},"role":{"$ref":"#/components/schemas/UserRole"},"username":{"type":"string"}},"required":["id","username","role","disabled","createdAt","lastSeenAt","projectIds"],"type":"object"},"UserRole":{"description":"admin: sees every project, manages users. sponsor: like a member, plus may invite new members.","enum":["admin","sponsor","member"],"type":"string"},"ApiKeyRecord":{"description":"A personal API key: acts as its owner (same projects and permissions). The key itself is shown once.","properties":{"createdAt":{"type":"string"},"expiresAt":{"description":"null = never expires.","type":["string","null"]},"id":{"type":"string"},"lastUsedAt":{"type":["string","null"]},"name":{"type":"string"},"prefix":{"description":"First characters of the key, to recognise it (\"slp_3fA9…\").","type":"string"},"scope":{"description":"\"read\": GET requests only. \"write\": everything the owner may do, except managing API keys.","enum":["read","write"],"type":"string"}},"required":["id","name","prefix","scope","createdAt","lastUsedAt","expiresAt"],"type":"object"},"ApiKeyCreate":{"properties":{"expiresInDays":{"type":["number","null"]},"name":{"type":"string"},"scope":{"enum":["read","write"],"type":"string"}},"required":["name"],"type":"object"},"ProjectRecord":{"description":"A slopner project = one Hetzner Cloud project token + an inference provider. Secrets are never returned, only hints.","properties":{"agentCount":{"type":"number"},"createdAt":{"type":"string"},"hetznerTokenHint":{"description":"Last 4 chars of the Hetzner Cloud API token, e.g. \"…a1b2\".","type":"string"},"id":{"type":"string"},"inference":{"properties":{"defaultModel":{"type":"string"},"keyHint":{"type":"string"},"provider":{"$ref":"#/components/schemas/InferenceProvider"}},"required":["provider","keyHint","defaultModel"],"type":"object"},"instanceCount":{"type":"number"},"myRole":{"description":"The current user's access: project owner, member, or \"admin\" (admins see every project).","enum":["owner","member","admin"],"type":"string"},"name":{"type":"string"},"objectStorage":{"anyOf":[{"properties":{"accessKeyHint":{"type":"string"},"region":{"type":"string"}},"required":["accessKeyHint","region"],"type":"object"},{"type":"null"}],"description":"Hetzner Object Storage (S3) credentials for bucket creation; null = not configured."},"sandboxCount":{"type":"number"},"usage":{"$ref":"#/components/schemas/Usage"}},"required":["id","name","createdAt","objectStorage","agentCount","sandboxCount","hetznerTokenHint","inference","instanceCount","usage","myRole"],"type":"object"},"InferenceProvider":{"const":"hetzner","description":"Hetzner Inference (OpenAI-compatible). The only provider; the field stays so others can be added.","type":"string"},"Usage":{"description":"costUsd: what the provider billed. estCostUsd: the same tokens at mock prices (Hetzner Inference bills nothing yet; never charged).","properties":{"costUsd":{"type":"number"},"estCostUsd":{"type":"number"},"inputTokens":{"type":"number"},"outputTokens":{"type":"number"}},"required":["inputTokens","outputTokens","costUsd","estCostUsd"],"type":"object"},"ProjectCreate":{"description":"REST API of the control plane (all under /api; session cookie + `x-slopner-csrf: 1` on mutations). Errors: non-2xx with { error: string }. 401 = not signed in, 403 = no access, 429 = rate limited.\n\n  GET    /config                         -> { auth: \"accounts\"; secure: boolean; setup: boolean; publicUrl; sandboxUrl; publicUrlLooksLocal }          secure: this connection may sign in (HTTPS or localhost). setup: no accounts exist yet          (the first admin signs up with a code from `slopner invite --admin` on the server).\n\nAccounts (no session needed for login / register / invite lookup; all rate limited)   GET    /auth/me                        -> { user: UserRecord }   (401 when signed out)   POST   /auth/login                     { username, password } -> { user }   (sets the session cookie)   POST   /auth/register                  { code, username, password } -> { user }   (username 3–32 of a-z 0-9 . _ -; password ≥ 10)   GET    /auth/invites/:code             -> { valid: boolean; role?: UserRole; projectName?: string | null; expiresAt?: string }   POST   /auth/logout                    -> { ok: true }   POST   /auth/password                  { current, next } -> { ok: true }   (signs out the user's other sessions)   GET    /auth/sessions                  -> SessionRecord[]   DELETE /auth/sessions/:id              -> { ok: true }   Admins:   GET    /users                          -> UserRecord[]   PATCH  /users/:id                      { role?, disabled? } -> UserRecord   (disabling signs the user out)   DELETE /users/:id                      -> { ok: true }   Admins (any invite) and project owners (invites to their project, role member):   GET    /invites[?projectId=]           -> InviteRecord[]   POST   /invites                        InviteCreate -> { invite: InviteRecord; code: string }   (code shown once)   DELETE /invites/:id                    -> { ok: true }   (revoke)   Feedback:   POST   /feedback                       FeedbackCreate -> FeedbackRecord   (any signed-in user; ≤ 6 files, 8 MB each)   GET    /feedback[?status=]             -> FeedbackRecord[]   (admins; newest first)   GET    /feedback/:id/files/:fileId     -> the attachment   (admins)   POST   /feedback/:id/forward           -> FeedbackRecord   (admins; written to <repo>/feedback/inbox/<date>-<id>/ for Claude)   POST   /feedback/:id/dismiss           -> FeedbackRecord   (admins)   POST   /feedback/:id/reopen            -> FeedbackRecord   (admins)   Project members (owners and admins manage; members can list):   GET    /projects/:id/members           -> ProjectMember[]   POST   /projects/:id/members           { username, role? } -> ProjectMember   (add an existing account)   PATCH  /projects/:id/members/:userId   { role: \"owner\" | \"member\" } -> ProjectMember   DELETE /projects/:id/members/:userId   -> { ok: true }   (members can remove themselves = leave)\n\n  GET    /projects                       -> ProjectRecord[]   POST   /projects                       ProjectCreate -> ProjectRecord   (validates both credentials)   PATCH  /projects/:id                   ProjectUpdate -> ProjectRecord   DELETE /projects/:id                   -> { ok: true }   (409 while it has instances)   GET    /projects/:id/hetzner/options   -> HetznerOptions   POST   /hetzner/options                { hetznerToken } -> HetznerOptions   (before a project exists)   GET    /projects/:id/models            -> ModelOption[]   POST   /inference/models               { provider; key } -> ModelOption[] (validates the key)   GET    /instances?projectId=           -> InstanceRecord[]   POST   /instances                      InstanceCreate -> { instance: InstanceRecord }   DELETE /instances/:id                  -> { ok: true }   (also deletes the Hetzner server)   POST   /instances/:id/power            { action: \"poweron\" | \"poweroff\" | \"reboot\" | \"shutdown\" } -> { ok: true }   GET    /instances/:id/agents/:agentId/events -> InstanceEvent[] (chat history, oldest first)","properties":{"hetznerToken":{"type":"string"},"inference":{"properties":{"defaultModel":{"type":"string"},"key":{"type":"string"},"provider":{"$ref":"#/components/schemas/InferenceProvider"}},"required":["provider","key"],"type":"object"},"name":{"type":"string"}},"required":["name","hetznerToken","inference"],"type":"object"},"ModelOption":{"properties":{"contextLength":{"type":["number","null"]},"estPricing":{"anyOf":[{"properties":{"inputPerMTok":{"type":"number"},"outputPerMTok":{"type":"number"}},"required":["inputPerMTok","outputPerMTok"],"type":"object"},{"type":"null"}],"description":"Mock prices used for cost estimates while the provider is free (never billed)."},"id":{"type":"string"},"name":{"type":"string"},"pricing":{"anyOf":[{"properties":{"inputPerMTok":{"type":"number"},"outputPerMTok":{"type":"number"}},"required":["inputPerMTok","outputPerMTok"],"type":"object"},{"type":"null"}],"description":"USD per million tokens; null = unknown, 0 = free."},"vision":{"description":"Accepts image input.","type":"boolean"}},"required":["id","name","contextLength","vision","pricing"],"type":"object"},"ProjectUpdate":{"properties":{"hetznerToken":{"type":"string"},"inference":{"properties":{"defaultModel":{"type":"string"},"key":{"type":"string"},"provider":{"$ref":"#/components/schemas/InferenceProvider"}},"type":"object"},"name":{"type":"string"},"objectStorage":{"anyOf":[{"properties":{"accessKey":{"type":"string"},"region":{"type":"string"},"secretKey":{"type":"string"}},"required":["accessKey","secretKey"],"type":"object"},{"type":"null"}],"description":"Hetzner Object Storage S3 credentials (from the Console). null removes them."}},"type":"object"},"SandboxOptions":{"properties":{"defaultRegion":{"type":"string"},"defaultSize":{"$ref":"#/components/schemas/SandboxSize"},"regions":{"items":{"properties":{"city":{"type":"string"},"country":{"type":"string"},"id":{"type":"string"},"label":{"type":"string"}},"required":["id","label","city","country"],"type":"object"},"type":"array"},"sizes":{"items":{"properties":{"cpus":{"type":"number"},"diskGb":{"type":"number"},"hourlyEur":{"type":"number"},"id":{"$ref":"#/components/schemas/SandboxSize"},"label":{"type":"string"},"memoryGb":{"type":"number"},"monthlyEur":{"description":"Gross EUR in the default region (see `prices` for every region).","type":"number"},"prices":{"additionalProperties":{"properties":{"hourlyEur":{"type":"number"},"monthlyEur":{"type":"number"}},"required":["monthlyEur","hourlyEur"],"type":"object"},"description":"Gross EUR per region id; only regions where this size is available.","type":"object"},"regions":{"description":"Regions where this size is available.","items":{"type":"string"},"type":"array"}},"required":["id","label","cpus","memoryGb","diskGb","monthlyEur","hourlyEur","prices","regions"],"type":"object"},"type":"array"}},"required":["sizes","regions","defaultSize","defaultRegion"],"type":"object"},"SandboxSize":{"enum":["small","medium","large"],"type":"string"},"ProjectMember":{"properties":{"addedAt":{"type":"string"},"role":{"enum":["owner","member"],"type":"string"},"userId":{"type":"string"},"username":{"type":"string"}},"required":["userId","username","role","addedAt"],"type":"object"},"SandboxRecord":{"properties":{"agentIds":{"description":"Agents living on this sandbox (they share its files).","items":{"type":"string"},"type":"array"},"cost":{"$ref":"#/components/schemas/SandboxCost"},"costUsd":{"description":"Inference spend of its agents (USD).","type":"number"},"country":{"description":"ISO country code of the region, e.g. \"DE\".","type":"string"},"createdAt":{"type":"string"},"estCostUsd":{"description":"Estimated at mock prices (not billed).","type":"number"},"firewall":{"description":"slopner-managed Hetzner firewall: inbound is denied except these rules. id null = not created yet.","properties":{"id":{"type":["number","null"]},"rules":{"items":{"$ref":"#/components/schemas/FirewallRule"},"type":"array"}},"required":["id","rules"],"type":"object"},"hetznerId":{"type":["number","null"]},"hourlyEur":{"type":["number","null"]},"id":{"type":"string"},"image":{"type":"string"},"info":{"anyOf":[{"$ref":"#/components/schemas/SysInfo"},{"type":"null"}]},"ipv4":{"type":["string","null"]},"ipv6":{"type":["string","null"]},"lastSeenAt":{"type":["string","null"]},"monthlyEur":{"type":["number","null"]},"name":{"type":"string"},"oneShot":{"description":"One-shot: deleted (conversations kept) once its agent is done.","type":"boolean"},"oneShotDeleteAt":{"description":"Set while the one-shot deletion is scheduled (grace period).","type":["string","null"]},"ports":{"description":"Non-system listening TCP ports (latest scan).","items":{"$ref":"#/components/schemas/PortEntry"},"type":"array"},"projectId":{"type":"string"},"region":{"type":"string"},"regionLabel":{"type":"string"},"secretNames":{"items":{"type":"string"},"type":"array","deprecated":true,"description":"Deprecated: Kept so older console builds don't crash; use `secrets`."},"secrets":{"description":"Secrets attached to agents on this sandbox: name → agent ids.","items":{"properties":{"agentIds":{"items":{"type":"string"},"type":"array"},"name":{"type":"string"}},"required":["name","agentIds"],"type":"object"},"type":"array"},"serverType":{"description":"Advanced details.","type":"string"},"size":{"anyOf":[{"$ref":"#/components/schemas/SandboxSize"},{"type":"null"}]},"sleepAfterMin":{"description":"Snapshot and delete the server after this many idle minutes (null = never).","type":["number","null"]},"sleepBlocker":{"description":"Why it doesn't auto-sleep right now, if it would otherwise (\"a port is public\"…).","type":["string","null"]},"sleptAt":{"type":["string","null"]},"status":{"$ref":"#/components/schemas/SandboxStatus"},"statusDetail":{"type":["string","null"]}},"required":["id","projectId","name","size","region","regionLabel","country","status","statusDetail","monthlyEur","hourlyEur","agentIds","costUsd","estCostUsd","secrets","ports","firewall","createdAt","lastSeenAt","serverType","image","ipv4","ipv6","hetznerId","info","sleepAfterMin","sleepBlocker","sleptAt","oneShot","oneShotDeleteAt","cost"],"type":"object"},"SandboxCost":{"description":"What a sandbox has cost (EUR incl. VAT, as Hetzner bills it: servers per started hour, snapshots per GB-month).","properties":{"asleepGb":{"type":["number","null"]},"asleepMonthlyEur":{"type":["number","null"]},"awakeHourlyEur":{"type":["number","null"]},"awakeMonthlyEur":{"type":["number","null"]},"nextHourAt":{"type":["string","null"]},"since":{"type":["string","null"]},"soFarEur":{"type":"number"}},"required":["soFarEur","since","awakeHourlyEur","awakeMonthlyEur","asleepMonthlyEur","asleepGb","nextHourAt"],"type":"object"},"FirewallRule":{"description":"An inbound allow rule on a sandbox's firewall.","properties":{"agentId":{"description":"Agent that opened it (null = opened from the console).","type":["string","null"]},"createdAt":{"type":"string"},"description":{"type":"string"},"dns":{"description":"DNS records (A/AAAA → this sandbox) created when the port was made public.","items":{"properties":{"hostname":{"type":"string"},"name":{"type":"string"},"zone":{"type":"string"}},"required":["zone","name","hostname"],"type":"object"},"type":"array"},"port":{"type":"string"},"protocol":{"enum":["tcp","udp"],"type":"string"},"sources":{"description":"CIDRs; default everyone (\"0.0.0.0/0\", \"::/0\").","items":{"type":"string"},"type":"array"}},"required":["port","protocol","sources","description","agentId","createdAt"],"type":"object"},"SysInfo":{"properties":{"cpus":{"type":"number"},"daemonVersion":{"description":"Version of the slopner daemon now running (set by the control plane from the daemon's hello).","type":"string"},"hostname":{"type":"string"},"kernel":{"type":"string"},"memTotalMb":{"type":"number"},"os":{"type":"string"},"workspace":{"type":"string"}},"required":["hostname","os","kernel","cpus","memTotalMb","workspace"],"type":"object"},"PortEntry":{"properties":{"address":{"type":"string"},"agentId":{"description":"Agent that started it (via the runtime's environment, or the systemctl/docker command it ran).","type":["string","null"]},"command":{"description":"Full command line of the owning process.","type":["string","null"]},"container":{"description":"Docker container name, when the port belongs to a container.","type":["string","null"]},"cwd":{"type":["string","null"]},"firstSeenAt":{"type":"string"},"http":{"anyOf":[{"properties":{"contentType":{"type":["string","null"]},"status":{"type":"number"},"title":{"type":["string","null"]}},"required":["status","title","contentType"],"type":"object"},{"type":"null"}],"description":"Result of a quick HTTP GET / on the port (TCP only)."},"listensPublicly":{"description":"Listens on all interfaces (reachable if the firewall allows it).","type":"boolean"},"pid":{"type":["number","null"]},"port":{"type":"number"},"process":{"description":"\"name (pid)\" of the owning process, if known.","type":["string","null"]},"proto":{"type":"string"},"public":{"description":"Reachable from the internet: listens on all interfaces AND the sandbox firewall allows the port.","type":"boolean"},"system":{"description":"OS / platform listeners (sshd, DNS, the slopner runtime itself). Hidden by default.","type":"boolean"},"unit":{"description":"systemd unit the process belongs to, e.g. \"site.service\".","type":["string","null"]}},"required":["proto","address","port","process"],"type":"object"},"SandboxStatus":{"description":"provisioning: machine being created installing:   machine booted, runtime being installed (cloud-init) ready:        connected and usable offline:      was ready, currently unreachable (rebooting, powered off, network) error:        provisioning failed or never checked in (see statusDetail) deleting:     being destroyed","enum":["provisioning","installing","ready","offline","error","deleting","snapshotting","sleeping","waking","finished"],"type":"string"},"SandboxCreate":{"properties":{"name":{"type":"string"},"oneShot":{"type":"boolean"},"projectId":{"type":"string"},"region":{"type":"string"},"size":{"$ref":"#/components/schemas/SandboxSize"},"sleepAfterMin":{"type":["number","null"]}},"required":["projectId"],"type":"object"},"SandboxUpdate":{"properties":{"name":{"type":"string"},"sleepAfterMin":{"type":["number","null"]}},"type":"object"},"PortExpose":{"properties":{"dns":{"anyOf":[{"properties":{"name":{"type":"string"},"zone":{"type":"string"}},"required":["zone","name"],"type":"object"},{"type":"null"}]},"port":{"type":["number","string"]},"protocol":{"enum":["tcp","udp"],"type":"string"},"sources":{"items":{"type":"string"},"type":"array"}},"required":["port"],"type":"object"},"DomainRecord":{"description":"https://<domain>/ → a port on a sandbox: Caddy on the sandbox (80/443) terminates TLS and proxies.","properties":{"createdAt":{"type":"string"},"dns":{"anyOf":[{"properties":{"name":{"type":"string"},"zone":{"type":"string"}},"required":["zone","name"],"type":"object"},{"type":"null"}],"description":"Set when slopner created the A/AAAA records in Hetzner DNS (removed again on detach)."},"domain":{"type":"string"},"port":{"type":"number"},"sandboxId":{"type":"string"},"status":{"anyOf":[{"$ref":"#/components/schemas/DomainStatus"},{"type":"null"}]}},"required":["domain","sandboxId","port","dns","createdAt","status"],"type":"object"},"DomainStatus":{"properties":{"checkedAt":{"type":"string"},"dnsOk":{"description":"What public DNS answers vs. the sandbox's addresses.","type":"boolean"},"expected":{"items":{"type":"string"},"type":"array"},"found":{"items":{"type":"string"},"type":"array"},"httpsError":{"type":["string","null"]},"httpsOk":{"type":"boolean"},"httpsStatus":{"description":"HTTP status of https://<domain>/, or why it failed (no certificate yet, timeout…).","type":["number","null"]}},"required":["checkedAt","dnsOk","found","expected","httpsOk","httpsStatus","httpsError"],"type":"object"},"DomainAttach":{"properties":{"createDns":{"type":"boolean"},"domain":{"type":"string"},"port":{"type":"number"}},"required":["domain","port"],"type":"object"},"PreviewAccess":{"properties":{"mode":{"$ref":"#/components/schemas/PreviewVisibility"},"port":{"type":"number"},"sandboxId":{"type":"string"},"shareUrl":{"description":"Share link (mode \"link\").","type":["string","null"]},"updatedAt":{"type":["string","null"]},"url":{"description":"The preview's address (sign-in or link needed unless public).","type":"string"},"users":{"description":"Extra accounts (besides project members) that may open it in private mode.","items":{"properties":{"id":{"type":"string"},"username":{"type":"string"}},"required":["id","username"],"type":"object"},"type":"array"}},"required":["sandboxId","port","mode","users","shareUrl","url","updatedAt"],"type":"object"},"PreviewVisibility":{"description":"Who can open a port's preview. private (default): members of the project, plus `users` (other slopner accounts). link: anyone with the share link (rotating it, or going back to private, cuts off everyone who used the old one). public: anyone, no link needed.","enum":["private","link","public"],"type":"string"},"PortOverviewItem":{"description":"A listening port in the project-wide overview.","properties":{"address":{"type":"string"},"agentId":{"description":"Agent that started it (via the runtime's environment, or the systemctl/docker command it ran).","type":["string","null"]},"agentName":{"type":["string","null"]},"command":{"description":"Full command line of the owning process.","type":["string","null"]},"container":{"description":"Docker container name, when the port belongs to a container.","type":["string","null"]},"cwd":{"type":["string","null"]},"domains":{"description":"Custom domains routed to this port (https://<domain>/).","items":{"type":"string"},"type":"array"},"firstSeenAt":{"type":"string"},"http":{"anyOf":[{"properties":{"contentType":{"type":["string","null"]},"status":{"type":"number"},"title":{"type":["string","null"]}},"required":["status","title","contentType"],"type":"object"},{"type":"null"}],"description":"Result of a quick HTTP GET / on the port (TCP only)."},"listensPublicly":{"description":"Listens on all interfaces (reachable if the firewall allows it).","type":"boolean"},"pid":{"type":["number","null"]},"port":{"type":"number"},"previewUrl":{"description":"Console preview through the control plane (works for local-only ports too). This is a console path; open it via POST /api/previews, which returns the real (ticketed) URL.","type":["string","null"]},"previewVisibility":{"$ref":"#/components/schemas/PreviewVisibility","description":"Who can open the preview (see PreviewVisibility)."},"process":{"description":"\"name (pid)\" of the owning process, if known.","type":["string","null"]},"proto":{"type":"string"},"public":{"description":"Reachable from the internet: listens on all interfaces AND the sandbox firewall allows the port.","type":"boolean"},"publicAddress":{"description":"host:port to connect to from the internet (only when `public`), for any TCP service: game servers, databases…","type":["string","null"]},"publicUrl":{"description":"Direct http:// URL on the sandbox's public IP (only when `public` and the port answers HTTP).","type":["string","null"]},"sandboxId":{"type":"string"},"sandboxName":{"type":"string"},"system":{"description":"OS / platform listeners (sshd, DNS, the slopner runtime itself). Hidden by default.","type":"boolean"},"unit":{"description":"systemd unit the process belongs to, e.g. \"site.service\".","type":["string","null"]}},"required":["address","agentName","domains","port","previewUrl","previewVisibility","process","proto","publicAddress","publicUrl","sandboxId","sandboxName"],"type":"object"},"AgentRecord":{"properties":{"context":{"anyOf":[{"properties":{"limitTokens":{"type":"number"},"usedTokens":{"type":"number"}},"required":["usedTokens","limitTokens"],"type":"object"},{"type":"null"}],"description":"How full the conversation context is (after the last turn); null until the first turn. The runtime compacts automatically near the limit; /compact does it now."},"costUsd":{"description":"Inference spend (USD, metered at the proxy).","type":"number"},"createdAt":{"type":"string"},"effort":{"$ref":"#/components/schemas/Effort"},"estCostUsd":{"description":"Estimated at mock prices (not billed).","type":"number"},"goal":{"anyOf":[{"$ref":"#/components/schemas/GoalState"},{"type":"null"}]},"id":{"type":"string"},"instructions":{"description":"Standing instructions (appended to the harness system prompt).","type":"string"},"lastActiveAt":{"type":["string","null"]},"lastMessage":{"description":"Last assistant text, for list previews.","type":["string","null"]},"loops":{"items":{"$ref":"#/components/schemas/LoopState"},"type":"array"},"mcp":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"description":"MCP servers of its sandbox this agent gets (names); null = all of them."},"mode":{"description":"build = normal; plan = read-only analysis mode (no edits / commands that change things).","enum":["build","plan"],"type":"string"},"model":{"type":"string"},"name":{"type":"string"},"parentId":{"description":"Agent that created this one via orchestration tools, if any.","type":["string","null"]},"pendingApprovals":{"type":"number"},"permissionPolicy":{"$ref":"#/components/schemas/PermissionPolicy"},"projectId":{"type":"string"},"retry":{"anyOf":[{"$ref":"#/components/schemas/AgentRetry"},{"type":"null"}],"description":"Inference failed mid-turn; the runtime retries on its own (show it instead of looking frozen)."},"sandbox":{"properties":{"country":{"type":"string"},"hetznerId":{"type":["number","null"]},"id":{"description":"Instance id: use it for live.rpc(...) sandbox tools (fs, proc, svc, exec, mcp) and WS subscriptions.","type":"string"},"info":{"anyOf":[{"$ref":"#/components/schemas/SysInfo"},{"type":"null"}]},"ipv4":{"type":["string","null"]},"ipv6":{"type":["string","null"]},"monthlyEur":{"type":["number","null"]},"name":{"type":"string"},"region":{"type":"string"},"regionLabel":{"type":"string"},"serverType":{"description":"Advanced details; keep out of the main flow.","type":"string"},"size":{"anyOf":[{"$ref":"#/components/schemas/SandboxSize"},{"type":"null"}]},"status":{"$ref":"#/components/schemas/SandboxStatus"}},"required":["id","name","size","region","regionLabel","country","status","monthlyEur","serverType","ipv4","ipv6","hetznerId","info"],"type":"object"},"secretIds":{"description":"Secrets attached to this agent (ids into the project's secrets).","items":{"type":"string"},"type":"array"},"state":{"$ref":"#/components/schemas/AgentState"},"stateDetail":{"description":"Human-readable progress/error, e.g. \"Installing runtime\" or the error message.","type":["string","null"]},"vision":{"description":"The model accepts image input (attachments are sent to it).","type":"boolean"}},"required":["id","projectId","name","mode","vision","goal","loops","secretIds","mcp","context","instructions","model","effort","permissionPolicy","state","stateDetail","retry","parentId","createdAt","lastActiveAt","costUsd","estCostUsd","lastMessage","pendingApprovals","sandbox"],"type":"object"},"Effort":{"description":"How hard the model thinks (reasoning effort).","enum":["low","medium","high","xhigh","max"],"type":"string"},"GoalState":{"properties":{"deadline":{"description":"Time budget: the goal stops when this passes; null = none.","type":["string","null"]},"durationMs":{"description":"Length of the time budget (resuming a goal stopped by it grants a fresh one).","type":["number","null"]},"id":{"description":"Stable id (journal entries reference it). Goals saved before ids existed get one on first change.","type":"string"},"iterations":{"type":"number"},"maxIterations":{"description":"Turn budget; null = none (the default).","type":["number","null"]},"maxTokens":{"description":"Token budget (input + output across the goal's turns); null = none.","type":["number","null"]},"progress":{"description":"Latest progress note from the agent (goal_progress tool).","type":["string","null"]},"reason":{"description":"Why it stopped/paused, e.g. \"turn budget (25) used up\".","type":["string","null"]},"startedAt":{"type":"string"},"status":{"description":"active: the agent keeps getting continuation turns; done: it reported completion; paused/stopped by user or a budget.","enum":["active","paused","done","stopped"],"type":"string"},"summary":{"description":"Completion summary from the agent (goal_complete tool).","type":["string","null"]},"text":{"type":"string"},"tokensSince":{"description":"Token counting starts here (start, or a resume with a fresh budget).","type":"string"},"tokensUsed":{"description":"Tokens used so far, live (metered per model request, not only at the end of a turn).","type":"number"},"updatedAt":{"type":"string"}},"required":["text","status","iterations","maxIterations","progress","summary","startedAt","updatedAt"],"type":"object"},"LoopState":{"properties":{"createdAt":{"type":"string"},"id":{"type":"string"},"intervalSec":{"description":"Interval loops; 0 for scheduled ones.","type":"number"},"label":{"description":"Human description, e.g. \"every 10m\" or \"weekdays at 08:30 (Europe/Berlin)\".","type":"string"},"lastRunAt":{"type":["string","null"]},"nextRunAt":{"type":"string"},"prompt":{"type":"string"},"runs":{"type":"number"},"schedule":{"anyOf":[{"$ref":"#/components/schemas/LoopSchedule"},{"type":"null"}]}},"required":["id","prompt","intervalSec","runs","nextRunAt","lastRunAt","createdAt"],"type":"object"},"LoopSchedule":{"description":"Calendar schedule for a loop (instead of a fixed interval): at `time` (HH:MM, 24 h) in `tz` (IANA zone, e.g. \"Europe/Berlin\") on the given weekdays (0 = Sunday … 6 = Saturday). `/loop daily 09:00 …`, `/loop weekdays 08:30 …`, `/loop mon,thu 18:00 …`.","properties":{"days":{"items":{"type":"number"},"type":"array"},"time":{"type":"string"},"tz":{"type":"string"}},"required":["time","days","tz"],"type":"object"},"PermissionPolicy":{"enum":["auto","ask","classifier"],"type":"string"},"AgentRetry":{"description":"Inference failed mid-turn and the runtime is retrying (with backoff).","properties":{"attempt":{"type":"number"},"message":{"type":"string"},"nextAt":{"type":"string"}},"required":["attempt","message","nextAt"],"type":"object"},"AgentState":{"description":"provisioning: sandbox being created / booting (see stateDetail) starting:     sandbox ready, agent process starting idle:         waiting for a task/message working:      running a turn needs_input:  waiting for a human approval offline:      sandbox unreachable (rebooting, network) stopped:      agent process stopped (sandbox still exists; messaging restarts it) error:        provisioning or runtime failure (see stateDetail) deleting:     sandbox being destroyed","enum":["provisioning","starting","idle","working","needs_input","offline","stopped","error","deleting","sleeping","finished"],"type":"string"},"AgentCreate":{"properties":{"effort":{"$ref":"#/components/schemas/Effort"},"instructions":{"description":"Standing instructions / role.","type":"string"},"mcp":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"description":"MCP servers of the sandbox this agent gets (names); null = all (default)."},"mcpInstall":{"description":"MCP servers to add to the sandbox for this agent (e.g. from MCP_CATALOG); installed before the agent starts.","items":{"$ref":"#/components/schemas/McpServerSpec"},"type":"array"},"model":{"type":"string"},"name":{"description":"Optional; derived from the task when omitted.","type":"string"},"newSandbox":{"description":"Without sandboxId: create a new sandbox for the agent with these settings (convenience).","properties":{"name":{"type":"string"},"oneShot":{"type":"boolean"},"region":{"type":"string"},"size":{"$ref":"#/components/schemas/SandboxSize"},"sleepAfterMin":{"type":["number","null"]}},"type":"object"},"permissionPolicy":{"$ref":"#/components/schemas/PermissionPolicy"},"projectId":{"type":"string"},"sandboxId":{"description":"Spawn on this existing sandbox.","type":"string"},"secretIds":{"description":"Secrets to attach (env vars for this agent only).","items":{"type":"string"},"type":"array"},"task":{"description":"First task, sent as the first message once the sandbox is up.","type":"string"}},"required":["projectId"],"type":"object"},"McpServerSpec":{"anyOf":[{"properties":{"args":{"items":{"type":"string"},"type":"array"},"catalogId":{"type":"string"},"command":{"type":"string"},"description":{"type":"string"},"env":{"additionalProperties":{"type":"string"},"type":"object"},"name":{"type":"string"},"type":{"const":"stdio","type":"string"}},"required":["command","name","type"],"type":"object"},{"properties":{"catalogId":{"type":"string"},"description":{"type":"string"},"headers":{"additionalProperties":{"type":"string"},"type":"object"},"name":{"type":"string"},"type":{"const":"http","type":"string"},"url":{"type":"string"}},"required":["name","type","url"],"type":"object"}],"description":"An MCP server on a sandbox (shared registry; each agent picks which it gets, see AgentRecord.mcp). Values in `env` / `headers` may reference secrets as `${NAME}`: they are filled in per agent from the secrets attached to that agent, so a token only reaches agents allowed to use it."},"AgentUpdate":{"description":"REST API (all under /api; see the auth notes above):\n\nSandboxes   GET    /projects/:id/sandbox-options       -> SandboxOptions   GET    /sandboxes?projectId=               -> SandboxRecord[]  (newest first)   GET    /sandboxes/:id                      -> SandboxRecord   POST   /sandboxes                          SandboxCreate -> SandboxRecord   (~2 min until \"ready\")   PATCH  /sandboxes/:id                      SandboxUpdate -> SandboxRecord   POST   /sandboxes/:id/power                { action: \"reboot\" | \"poweroff\" | \"poweron\" } -> { ok: true }   DELETE /sandboxes/:id                      -> { ok: true }   (destroys the machine AND every agent on it)\n\nAgents   GET    /agents?projectId=[&sandboxId=]     -> AgentRecord[]   (newest first)   GET    /agents/:id                         -> AgentRecord   POST   /agents                             AgentCreate -> AgentRecord   (sandboxId, or newSandbox to create one)   PATCH  /agents/:id                         AgentUpdate -> AgentRecord   DELETE /agents/:id                         -> { ok: true }   (removes the agent; its sandbox stays)   POST   /agents/:id/messages                { text; attachments?: Attachment[]; formResponse?: { callId; values } } -> { ok: true; queued: boolean; message: ChatMessage | null }                                              (≤ 4 attachments, ≤ 5 MB each; images reach vision models, all files land in <workspace>/uploads/)   GET    /agents/:id/commands                -> CommandInfo[]   (for the \"/\" menu)   POST   /agents/:id/commands                { name; args?: string } -> { ok: true; output: string | null }                                              (output: markdown, also stored as a system ChatMessage)                                              (queued while the sandbox isn't ready; message = the queued message)   POST   /agents/:id/interrupt               -> { ok: true }   POST   /agents/:id/stop                    -> { ok: true }   POST   /agents/:id/approvals/:requestId    { allow: boolean; message?: string } -> { ok: true }   GET    /agents/:id/messages                -> ChatMessage[] (chat history, oldest first, latest version of each message; includes queued ones)\n\nSecrets (values are write-only)   GET    /secrets?projectId=                 -> SecretRecord[]   POST   /secrets                            SecretCreate -> SecretRecord   PATCH  /secrets/:id                        SecretUpdate -> SecretRecord   DELETE /secrets/:id                        -> { ok: true }   Attach to agents with AgentCreate/AgentUpdate.secretIds. Delivery: env vars for that agent's runtime   only (applied from its next message) and /var/lib/slopner/secrets/<agentId>.env (root-only, for   services the agent sets up). Values are redacted from chat transcripts on the whole sandbox.\n\nPorts   GET    /ports?projectId=[&sandboxId=][&all=1] -> PortOverviewItem[]  (all=1 includes system listeners)   POST   /previews                          { sandboxId, port, path? } -> { url }          Preview URL for a sandbox port: per-port subdomain (SLOPNER_PREVIEW_DOMAIN) or          <sandbox URL>/preview/:sandboxId/:port/ — never the console origin, so previewed apps          can't script the console. The URL carries a short-lived ticket that becomes a cookie for          that preview only. Previews stream (SSE, downloads) and proxy WebSockets.\n\nTasks   GET    /tasks?projectId=[&agentId=]        -> TaskRecord[]   (agentId: assigned to or reviewed by)   GET    /tasks/:id                         -> TaskRecord   POST   /tasks                             TaskCreate -> TaskRecord   PATCH  /tasks/:id                         TaskUpdate -> TaskRecord   DELETE /tasks/:id                         -> { ok: true }   POST   /tasks/:id/start                   -> TaskRecord   (todo/blocked → in_progress; sends it to the assignee)   POST   /tasks/:id/review                  { approve: boolean; notes?: string } -> TaskRecord   (user verdict on a task in review)   POST   /tasks/:id/comment                 { text } -> TaskRecord   (forwarded to the assignee while it works; unblocks a blocked task)   POST   /tasks/:id/cancel                  -> TaskRecord   POST   /tasks/:id/reopen                  { notes? } -> TaskRecord   (done/failed/cancelled → todo)   Live updates: UiDownFrame { t: \"task\", task } / { t: \"task.deleted\", taskId }.\n\nGoals   GET    /agents/:id/goals                  -> GoalJournal[]   (current first, then past goals)   /goal <objective> [--turns N] [--tokens 500k|2M] [--for 30m|2h|1d]   budgets (chat command)   /loop daily 09:00 <prompt> · /loop weekdays 08:30 <prompt> · /loop mon,thu 18:00 <prompt>   POST /agents/:id/commands accepts { name, args, tz? } (tz: the browser's IANA zone, for schedules).\n\nPreview access (per port; see PreviewVisibility)   GET    /sandboxes/:id/previews/:port      -> PreviewAccess   PUT    /sandboxes/:id/previews/:port      { mode, usernames? } -> PreviewAccess   (project owners/members)   POST   /sandboxes/:id/previews/:port/rotate -> PreviewAccess   (new share link; the old one stops working)   POST   /previews                          also works for accounts in `users` (not project members);          a signed-out visitor of a non-public preview is sent to <console>/#/open-preview/<sandbox>/<port>.\n\nPublic ports (services are reachable only through previews until made public)   GET    /projects/:id/dns-zones            -> { id: number; name: string }[]   (Hetzner DNS zones of the project)   POST   /sandboxes/:id/expose              PortExpose -> { sandbox: SandboxRecord; hostname: string | null; url: string }   POST   /sandboxes/:id/unexpose            { port, protocol?, removeDns?: boolean (default true) } -> SandboxRecord   Agents' firewall_open_port becomes an Inbox request; the user approves it.\n\nNotifications (Inbox → Notifications)   GET    /notifications?projectId=          -> NotificationChannel[]   POST   /notifications                     NotificationChannelCreate -> NotificationChannel   PATCH  /notifications/:id                 NotificationChannelUpdate -> NotificationChannel   DELETE /notifications/:id                 -> { ok: true }   POST   /notifications/:id/test            -> { ok: true } | 502 { error }   (sends a test message now)   Defaults for new channels: approval.requested, goal.completed, agent.failed, sandbox.failed.\n\nFirewall (per sandbox; inbound denied except these rules)   POST   /sandboxes/:id/firewall/rules           { port; protocol?: \"tcp\"|\"udp\"; sources?: string[]; description? } -> SandboxRecord   DELETE /sandboxes/:id/firewall/rules/:protocol/:port   -> SandboxRecord\n\nProject feeds   GET    /inbox?projectId=                   -> InboxItem[]   GET    /activity?projectId=&limit=[&sandboxId=][&agentId=][&before=<id>] -> ActivityItem[] (newest first; before = page older than that id)\n\nLive: subscribe to a sandbox id ({t:\"sub\", instanceId: sandbox.id}) for its agents' chat events and metrics; {t:\"sandbox\"}, {t:\"sandbox.deleted\"}, {t:\"agent\"}, {t:\"agent.deleted\"}, {t:\"activity\"}, {t:\"inbox\"} frames go to every UI. Sandbox tools use live.rpc(sandbox.id, \"fs.list\" | \"proc.list\" | \"ports.list\" | \"svc.*\" | \"exec.run\" | \"mcp.*\" | \"sys.metrics\", …).","properties":{"instructions":{"type":"string"},"mcp":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"description":"MCP servers of the sandbox this agent gets (names); null = all (default)."},"mode":{"enum":["build","plan"],"type":"string"},"model":{"type":"string"},"name":{"type":"string"},"permissionPolicy":{"$ref":"#/components/schemas/PermissionPolicy"},"secretIds":{"description":"Replaces the attached secrets.","items":{"type":"string"},"type":"array"}},"type":"object"},"ChatMessage":{"properties":{"command":{"description":"For system notices: the command that produced it, e.g. { name: \"compact\", args: \"\" }.","properties":{"args":{"type":"string"},"name":{"type":"string"}},"required":["name","args"],"type":"object"},"createdAt":{"type":"string"},"formResponse":{"description":"User message that answers an HTML form the agent showed (tool part with callId).","properties":{"callId":{"type":"string"},"values":{"additionalProperties":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}]},"type":"object"}},"required":["callId","values"],"type":"object"},"fromAgent":{"description":"Set on user messages sent by another agent (orchestration).","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"],"type":"object"},"id":{"type":"string"},"model":{"description":"For assistant messages.","type":"string"},"origin":{"description":"Messages slopner sent on the user's behalf: goal continuations and loop runs.","enum":["goal","loop","recovery","task"],"type":"string"},"parts":{"items":{"$ref":"#/components/schemas/ChatPart"},"type":"array"},"queued":{"description":"User message accepted by the control plane but not yet delivered (sandbox still provisioning).","type":"boolean"},"role":{"description":"\"system\" = slopner notices (command results, goal/loop events); render as a centered notice, not a bubble.","enum":["user","assistant","system"],"type":"string"}},"required":["id","role","parts","createdAt"],"type":"object"},"ChatPart":{"anyOf":[{"properties":{"text":{"type":"string"},"type":{"const":"text","type":"string"}},"required":["type","text"],"type":"object"},{"properties":{"mime":{"type":"string"},"name":{"type":"string"},"type":{"const":"image","type":"string"},"url":{"type":"string"}},"required":["type","url","mime"],"type":"object"},{"properties":{"mime":{"type":"string"},"name":{"type":"string"},"path":{"type":["string","null"]},"type":{"const":"file","type":"string"}},"required":["type","name","mime","path"],"type":"object"},{"properties":{"text":{"type":"string"},"type":{"const":"reasoning","type":"string"}},"required":["type","text"],"type":"object"},{"properties":{"callId":{"type":"string"},"error":{"type":"string"},"input":{"additionalProperties":{},"type":"object"},"output":{"type":"string"},"status":{"enum":["pending","running","completed","error"],"type":"string"},"title":{"description":"Short human summary when the runtime provides one (e.g. the command or file path).","type":"string"},"tool":{"description":"e.g. \"bash\", \"read\", \"write\", \"edit\", \"grep\", \"glob\", \"webfetch\", or \"<mcp server>_<tool>\".","type":"string"},"type":{"const":"tool","type":"string"}},"required":["type","callId","tool","input","status"],"type":"object"}]},"Attachment":{"description":"File attached to a chat message (base64). Images go to the model if it supports vision; every file is saved in the sandbox under uploads/.","properties":{"data":{"type":"string"},"mime":{"type":"string"},"name":{"type":"string"}},"required":["name","mime","data"],"type":"object"},"CommandInfo":{"description":"source: \"slopner\" (control plane), \"runtime\" (the agent runtime's own commands, including custom ones from the workspace), \"ui\" (handled in the browser, e.g. /export).","properties":{"args":{"type":"string"},"description":{"type":"string"},"name":{"type":"string"},"source":{"enum":["slopner","runtime","ui"],"type":"string"}},"required":["name","description","source"],"type":"object"},"GoalJournal":{"description":"A goal and its journal (Agent → Goals).","properties":{"events":{"items":{"properties":{"at":{"type":"string"},"kind":{"enum":["started","progress","turn","blocked","paused","resumed","completed","stopped","cleared"],"type":"string"},"text":{"type":["string","null"]},"tokens":{"type":"number"}},"required":["at","kind","text"],"type":"object"},"type":"array"},"goal":{"properties":{"deadline":{"description":"Time budget: the goal stops when this passes; null = none.","type":["string","null"]},"durationMs":{"description":"Length of the time budget (resuming a goal stopped by it grants a fresh one).","type":["number","null"]},"id":{"description":"Stable id (journal entries reference it). Goals saved before ids existed get one on first change.","type":"string"},"iterations":{"type":"number"},"maxIterations":{"description":"Turn budget; null = none (the default).","type":["number","null"]},"maxTokens":{"description":"Token budget (input + output across the goal's turns); null = none.","type":["number","null"]},"progress":{"description":"Latest progress note from the agent (goal_progress tool).","type":["string","null"]},"reason":{"description":"Why it stopped/paused, e.g. \"turn budget (25) used up\".","type":["string","null"]},"startedAt":{"type":"string"},"status":{"description":"active: the agent keeps getting continuation turns; done: it reported completion; paused/stopped by user or a budget.","enum":["active","paused","done","stopped"],"type":"string"},"summary":{"description":"Completion summary from the agent (goal_complete tool).","type":["string","null"]},"text":{"type":"string"},"tokensSince":{"description":"Token counting starts here (start, or a resume with a fresh budget).","type":"string"},"tokensUsed":{"description":"Tokens used so far, live (metered per model request, not only at the end of a turn).","type":"number"},"updatedAt":{"type":"string"}},"required":["id","iterations","maxIterations","progress","startedAt","status","summary","text","updatedAt"],"type":"object"}},"required":["goal","events"],"type":"object"},"TaskRecord":{"properties":{"acceptance":{"description":"Acceptance criteria, one per entry.","items":{"type":"string"},"type":"array"},"assigneeId":{"type":["string","null"]},"assigneeName":{"type":["string","null"]},"attempts":{"description":"Times the assignee submitted it.","type":"number"},"blockedReason":{"description":"Latest blocker.","type":["string","null"]},"createdAt":{"type":"string"},"createdBy":{"anyOf":[{"properties":{"kind":{"const":"user","type":"string"}},"required":["kind"],"type":"object"},{"properties":{"id":{"type":"string"},"kind":{"const":"agent","type":"string"},"name":{"type":"string"}},"required":["kind","id","name"],"type":"object"}]},"description":{"description":"Markdown.","type":"string"},"finishedAt":{"type":["string","null"]},"history":{"items":{"$ref":"#/components/schemas/TaskEvent"},"type":"array"},"id":{"type":"string"},"number":{"description":"Per-project number: shown as T-<number>.","type":"number"},"projectId":{"type":"string"},"result":{"description":"The assignee's latest completion report (markdown).","type":["string","null"]},"reviewNotes":{"description":"Latest reviewer notes (on approve or reject).","type":["string","null"]},"reviewerId":{"description":"Agent that checks the result against the acceptance criteria; null = no review step.","type":["string","null"]},"reviewerName":{"type":["string","null"]},"startedAt":{"type":["string","null"]},"status":{"$ref":"#/components/schemas/TaskStatus"},"title":{"type":"string"},"turns":{"description":"Continuation turns spent on it.","type":"number"},"updatedAt":{"type":"string"}},"required":["id","projectId","number","title","description","acceptance","status","assigneeId","assigneeName","reviewerId","reviewerName","createdBy","result","reviewNotes","blockedReason","attempts","turns","createdAt","updatedAt","startedAt","finishedAt","history"],"type":"object"},"TaskEvent":{"properties":{"at":{"type":"string"},"by":{"description":"\"You\" or the agent's name.","type":"string"},"kind":{"enum":["created","edited","assigned","started","submitted","approved","rejected","blocked","failed","reopened","cancelled","comment"],"type":"string"},"text":{"type":["string","null"]}},"required":["at","kind","by","text"],"type":"object"},"TaskStatus":{"description":"kind \"tool\": the agent runtime waits for permission to run a tool (approval.*). kind \"resource\": the agent asked for a Hetzner change that needs your OK (paid resources, deletions, overwriting DNS records). approval.toolName = \"hetzner:<action>\", approval.input = details; `resource` carries a title, description and price. Answer both kinds with POST /agents/:id/approvals/:requestId.","enum":["todo","in_progress","blocked","review","done","failed","cancelled"],"type":"string"},"TaskCreate":{"properties":{"acceptance":{"items":{"type":"string"},"type":"array"},"assigneeId":{"type":["string","null"]},"description":{"type":"string"},"projectId":{"type":"string"},"reviewerId":{"type":["string","null"]},"start":{"description":"Send it to the assignee right away (needs assigneeId).","type":"boolean"},"title":{"type":"string"}},"required":["projectId","title"],"type":"object"},"TaskUpdate":{"properties":{"acceptance":{"items":{"type":"string"},"type":"array"},"assigneeId":{"type":["string","null"]},"description":{"type":"string"},"reviewerId":{"type":["string","null"]},"title":{"type":"string"}},"type":"object"},"LibraryInfo":{"properties":{"bucket":{"description":"null until the library is set up (slopner creates one bucket for it).","type":["string","null"]},"files":{"items":{"$ref":"#/components/schemas/LibraryFile"},"type":"array"},"objectStorage":{"description":"Object Storage credentials are configured for the project (needed to set it up).","type":"boolean"},"region":{"type":["string","null"]},"totalBytes":{"type":"number"}},"required":["bucket","region","objectStorage","files","totalBytes"],"type":"object"},"LibraryFile":{"properties":{"addedBy":{"anyOf":[{"properties":{"kind":{"const":"user","type":"string"},"name":{"type":"string"}},"required":["kind","name"],"type":"object"},{"properties":{"id":{"type":"string"},"kind":{"const":"agent","type":"string"},"name":{"type":"string"}},"required":["kind","id","name"],"type":"object"}]},"createdAt":{"type":"string"},"description":{"type":"string"},"mime":{"type":"string"},"path":{"description":"Path inside the library, e.g. \"briefs/q3-plan.pdf\" (no leading slash).","type":"string"},"pinned":{"description":"Pinned files are in every agent's context: listed with their description, small text files with their content.","type":"boolean"},"size":{"type":"number"},"updatedAt":{"type":"string"}},"required":["path","size","mime","description","pinned","addedBy","createdAt","updatedAt"],"type":"object"},"LibrarySetup":{"properties":{"region":{"enum":["fsn1","nbg1","hel1"],"type":"string"}},"required":["region"],"type":"object"},"LibraryUpdate":{"description":"Rename/move (`path`), describe or pin a file.","properties":{"description":{"type":"string"},"path":{"type":"string"},"pinned":{"type":"boolean"}},"type":"object"},"SecretRecord":{"description":"Values are write-only: the API never returns them, only a hint. Secrets don't reach anything until attached to an agent (AgentCreate/AgentUpdate.secretIds).","properties":{"agentIds":{"description":"Agents this secret is attached to (read-only here; attach via the agent).","items":{"type":"string"},"type":"array"},"createdAt":{"type":"string"},"description":{"type":"string"},"hint":{"description":"Last 4 characters, e.g. \"…a1b2\".","type":"string"},"id":{"type":"string"},"name":{"description":"Environment variable name, e.g. GITHUB_TOKEN ([A-Z_][A-Z0-9_]*).","type":"string"},"projectId":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","projectId","name","description","hint","agentIds","createdAt","updatedAt"],"type":"object"},"SecretCreate":{"properties":{"description":{"type":"string"},"name":{"type":"string"},"projectId":{"type":"string"},"value":{"type":"string"}},"required":["projectId","name","value"],"type":"object"},"SecretUpdate":{"properties":{"description":{"type":"string"},"value":{"type":"string"}},"type":"object"},"InboxItem":{"properties":{"agentId":{"type":"string"},"agentName":{"type":"string"},"approval":{"$ref":"#/components/schemas/ApprovalRequest"},"createdAt":{"type":"string"},"kind":{"enum":["tool","resource"],"type":"string"},"resource":{"properties":{"costHourlyEur":{"type":["number","null"]},"costMonthlyEur":{"type":["number","null"]},"description":{"type":"string"},"title":{"type":"string"}},"required":["title","description","costMonthlyEur","costHourlyEur"],"type":"object"},"sandboxId":{"type":"string"}},"required":["kind","agentId","agentName","sandboxId","approval","createdAt"],"type":"object"},"ApprovalRequest":{"properties":{"input":{"additionalProperties":{},"type":"object"},"reason":{"type":"string"},"requestId":{"type":"string"},"toolName":{"type":"string"}},"required":["requestId","toolName","input"],"type":"object"},"ActivityItem":{"properties":{"agentId":{"type":["string","null"]},"agentName":{"type":["string","null"]},"createdAt":{"type":"string"},"id":{"type":"number"},"kind":{"enum":["sandbox.created","sandbox.ready","sandbox.deleted","sandbox.slept","sandbox.woke","agent.created","secret.changed","agent.completed","agent.failed","approval.requested","approval.resolved","agent.message","sandbox.failed","hetzner.changed","library.changed","resource.requested","resource.resolved","goal.set","goal.completed","goal.stopped","task.created","task.submitted","task.done","task.rejected","task.blocked","task.failed","port.opened","port.closed","agent.deleted"],"type":"string"},"meta":{"anyOf":[{"additionalProperties":{},"type":"object"},{"type":"null"}]},"projectId":{"type":"string"},"sandboxId":{"description":"Set for sandbox-level items (and agent items, for context).","type":["string","null"]},"sandboxName":{"type":["string","null"]},"text":{"type":["string","null"]}},"required":["id","projectId","agentId","agentName","sandboxId","sandboxName","kind","text","meta","createdAt"],"type":"object"},"NotificationChannel":{"properties":{"config":{"additionalProperties":{"type":"string"},"description":"Non-secret config fields.","type":"object"},"createdAt":{"type":"string"},"enabled":{"type":"boolean"},"events":{"items":{"$ref":"#/components/schemas/NotificationEvent"},"type":"array"},"id":{"type":"string"},"lastError":{"description":"Last delivery error (cleared on success).","type":["string","null"]},"lastSentAt":{"type":["string","null"]},"name":{"type":"string"},"projectId":{"type":"string"},"secretHints":{"additionalProperties":{"type":"string"},"description":"Secret fields that are set, as hints (\"…ab12\").","type":"object"},"type":{"$ref":"#/components/schemas/NotificationChannelType"}},"required":["id","projectId","name","type","enabled","events","config","secretHints","createdAt","lastSentAt","lastError"],"type":"object"},"NotificationEvent":{"enum":["approval.requested","agent.completed","goal.completed","task.done","attention","agent.failed","port.public","sandbox.failed"],"type":"string"},"NotificationChannelType":{"enum":["telegram","discord","slack","email","webhook"],"type":"string"},"NotificationChannelCreate":{"description":"`config` holds all fields, secret ones included; on update, omitted secret fields keep their value.","properties":{"config":{"additionalProperties":{"type":"string"},"type":"object"},"enabled":{"type":"boolean"},"events":{"items":{"$ref":"#/components/schemas/NotificationEvent"},"type":"array"},"name":{"type":"string"},"projectId":{"type":"string"},"type":{"$ref":"#/components/schemas/NotificationChannelType"}},"required":["projectId","name","type","config"],"type":"object"},"NotificationChannelUpdate":{"properties":{"config":{"additionalProperties":{"type":"string"},"type":"object"},"enabled":{"type":"boolean"},"events":{"items":{"$ref":"#/components/schemas/NotificationEvent"},"type":"array"},"name":{"type":"string"}},"type":"object"},"InviteRecord":{"properties":{"createdAt":{"type":"string"},"createdBy":{"type":"string"},"expiresAt":{"type":"string"},"id":{"type":"string"},"maxUses":{"type":"number"},"note":{"type":"string"},"projectId":{"description":"Also makes the new user a member of this project.","type":["string","null"]},"projectName":{"type":["string","null"]},"role":{"$ref":"#/components/schemas/UserRole","description":"Role of the account it creates."},"status":{"$ref":"#/components/schemas/InviteStatus"},"usedBy":{"description":"Usernames of the accounts created with it.","items":{"type":"string"},"type":"array"},"uses":{"type":"number"}},"required":["id","role","projectId","projectName","note","maxUses","uses","usedBy","expiresAt","createdAt","createdBy","status"],"type":"object"},"InviteStatus":{"enum":["active","used","expired","revoked"],"type":"string"},"InviteCreate":{"properties":{"expiresInDays":{"description":"Default 7, at most 90.","type":"number"},"maxUses":{"description":"Default 1.","type":"number"},"note":{"type":"string"},"projectId":{"type":["string","null"]},"role":{"$ref":"#/components/schemas/UserRole"}},"type":"object"},"FeedbackCreate":{"properties":{"attachments":{"items":{"properties":{"data":{"type":"string"},"mime":{"type":"string"},"name":{"type":"string"}},"required":["name","mime","data"],"type":"object"},"type":"array"},"page":{"type":"string"},"text":{"type":"string"}},"required":["text"],"type":"object"},"FeedbackRecord":{"properties":{"attachments":{"items":{"$ref":"#/components/schemas/FeedbackAttachment"},"type":"array"},"createdAt":{"type":"string"},"handledAt":{"type":["string","null"]},"handledBy":{"type":["string","null"]},"id":{"type":"string"},"page":{"description":"Console page it was sent from (hash route).","type":"string"},"status":{"$ref":"#/components/schemas/FeedbackStatus"},"text":{"type":"string"},"userAgent":{"type":["string","null"]},"username":{"type":"string"}},"required":["id","username","page","text","attachments","status","createdAt","handledAt","handledBy","userAgent"],"type":"object"},"FeedbackAttachment":{"properties":{"id":{"type":"string"},"mime":{"type":"string"},"name":{"type":"string"},"size":{"type":"number"}},"required":["id","name","mime","size"],"type":"object"},"FeedbackStatus":{"enum":["new","forwarded","dismissed"],"type":"string"}}},"security":[{"apiKey":[]}],"servers":[{"url":"https://api.slopner.lol/v1"}]}