{"openapi":"3.1.0","info":{"title":"ROK Backend","version":"1.0.0"},"paths":{"/api/workspace/list":{"post":{"summary":"Workspace List","description":"Recursive file tree of the caller's workspace.","operationId":"workspace_list_api_workspace_list_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceBrowseRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Workspace List Api Workspace List Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/workspace/read":{"post":{"summary":"Workspace Read","description":"Read one file: text (UTF-8), PDF (base64), or 400 for other binaries.","operationId":"workspace_read_api_workspace_read_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceBrowseRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Workspace Read Api Workspace Read Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/workspace/write":{"post":{"summary":"Workspace Write","description":"Create/overwrite a UTF-8 text file in the caller's workspace.","operationId":"workspace_write_api_workspace_write_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceBrowseRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Workspace Write Api Workspace Write Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/workspace/mkdir":{"post":{"summary":"Workspace Mkdir","description":"Create a directory (parents included) in the caller's workspace.","operationId":"workspace_mkdir_api_workspace_mkdir_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceBrowseRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Workspace Mkdir Api Workspace Mkdir Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/workspace/rename":{"post":{"summary":"Workspace Rename","description":"Rename/move a file or folder, strictly inside the sandbox.","operationId":"workspace_rename_api_workspace_rename_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceBrowseRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Workspace Rename Api Workspace Rename Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/workspace/delete":{"post":{"summary":"Workspace Delete","description":"Delete a file or folder (recursive=true for non-empty folders).","operationId":"workspace_delete_api_workspace_delete_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceBrowseRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Workspace Delete Api Workspace Delete Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/health":{"get":{"summary":"Health","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object","title":"Response Health Health Get"}}}}}}},"/api/shares":{"post":{"summary":"Create Share","description":"Create a share link from a serialized conversation.\n\nThe full conversation (messages incl. tool calls, thinking traces, file\nmarkers, generated images) is stored server-side so the public /share/<id>\npage can render it read-only for anyone with the link.","operationId":"create_share_api_shares_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShareCreateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/shares/{share_id}":{"get":{"summary":"Get Share","description":"Fetch a share. Revoked shares return 404 so old links die immediately.\n\nNOTE: 'authenticated' shares are gated at the Next.js layer (the share page\nchecks the Auth.js session before calling this endpoint). This endpoint\nitself is unauthenticated by design — same as the rest of the backend.","operationId":"get_share_api_shares__share_id__get","parameters":[{"name":"share_id","in":"path","required":true,"schema":{"type":"string","title":"Share Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"summary":"Update Share","description":"Update a share's access mode without changing its link (owner only).","operationId":"update_share_api_shares__share_id__patch","parameters":[{"name":"share_id","in":"path","required":true,"schema":{"type":"string","title":"Share Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShareUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Revoke Share","description":"Revoke a share, immediately invalidating the public link (owner only).","operationId":"revoke_share_api_shares__share_id__delete","parameters":[{"name":"share_id","in":"path","required":true,"schema":{"type":"string","title":"Share Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/generate-title":{"post":{"summary":"Generate Title","description":"Generate a short chat title from the first exchange using gemma4:31b-cloud.","operationId":"generate_title_api_generate_title_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateTitleRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object","title":"Response Generate Title Api Generate Title Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/chat":{"post":{"summary":"Chat","operationId":"chat_api_chat_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/quota":{"post":{"summary":"Token Quota","description":"Current daily credit-budget usage for the caller (read-only, charges\nnothing): total used, remaining, and the limit — plus the separate daily\nIMAGE budget (a count, 1 per image) and the per-model credit costs so\nthe model picker can show what each model spends.","operationId":"token_quota_api_quota_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuotaRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/chat/completions":{"post":{"tags":["openai-compat"],"summary":"Chat Completions","description":"OpenAI-compatible chat completion with native tool-call passthrough.","operationId":"chat_completions_v1_chat_completions_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/_V1ChatRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Chat Completions V1 Chat Completions Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/models":{"get":{"tags":["openai-compat"],"summary":"List Models","description":"List the models available via /v1/chat/completions (OpenAI shape).\n\nBehind the same key wall as the rest of /v1 (V1_REQUIRE_KEY). Deliberately\nNOT rate limited — it returns a static list, so SDKs/clients can probe it\nfreely (with a valid key once the wall is armed).","operationId":"list_models_v1_models_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response List Models V1 Models Get"}}}}}}},"/internal/api-key-usage":{"post":{"tags":["internal"],"summary":"Api Key Usage","description":"Per-key /v1 usage plus the shared ACCOUNT budget, for the dashboard.\n\nCalled by the Next.js proxy (never the browser directly) with the shared\nbackend secret, exactly like the other /internal-style endpoints. It only\never reports counters the caller already supplies ids for, so it exposes\nnothing about anyone else's keys, and unknown ids simply come back zeroed.\n\nTwo scopes come back, and they are NOT interchangeable:\n\n  * ``keys`` — requestsToday / completionsToday per key ROW ID. A display\n    breakdown, so the owner can see which key is doing the work.\n  * ``account`` — the enforced daily budget, shared by every key on the\n    account (null when no accountId was supplied). This is the only place\n    \"remaining today\" is reported, because a per-key remainder would\n    imply a per-key ceiling that no longer exists — and that a second key\n    could multiply.\n\nThe numbers are IN-MEMORY and reset when the backend restarts (a deploy) —\ndeliberately, because they are the same counters that enforce the daily\nbudget, so the dashboard shows what the user is actually being held to\nrather than a separate accounting that could disagree with it. The response\nflags this with ``inMemory`` so the UI can say so instead of implying a\ndurable usage history.","operationId":"api_key_usage_internal_api_key_usage_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/_V1UsageRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Api Key Usage Internal Api Key Usage Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"ApiMessage":{"properties":{"role":{"type":"string","title":"Role"},"content":{"type":"string","title":"Content"}},"type":"object","required":["role","content"],"title":"ApiMessage"},"ChatParams":{"properties":{"temperature":{"type":"number","title":"Temperature","default":0.7},"topP":{"type":"number","title":"Topp","default":0.9},"topK":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Topk"},"maxTokens":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Maxtokens"},"frequencyPenalty":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Frequencypenalty"},"presencePenalty":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Presencepenalty"}},"type":"object","title":"ChatParams"},"ChatRequest":{"properties":{"model":{"type":"string","title":"Model"},"messages":{"items":{"$ref":"#/components/schemas/ApiMessage"},"type":"array","title":"Messages"},"params":{"$ref":"#/components/schemas/ChatParams"},"system":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"System"},"stream":{"type":"boolean","title":"Stream","default":true},"baseUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Baseurl"},"apiKey":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Apikey"},"provider":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider"},"images":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Images"},"files":{"anyOf":[{"items":{"$ref":"#/components/schemas/UploadedFile"},"type":"array"},{"type":"null"}],"title":"Files"},"thinkingEffort":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Thinkingeffort"},"clientTime":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Clienttime"},"guest":{"type":"boolean","title":"Guest","default":false},"userId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Userid"},"anonId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Anonid"}},"type":"object","required":["model","messages","params"],"title":"ChatRequest"},"GenerateTitleRequest":{"properties":{"user_message":{"type":"string","title":"User Message"},"assistant_snippet":{"type":"string","title":"Assistant Snippet","default":""}},"type":"object","required":["user_message"],"title":"GenerateTitleRequest"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"QuotaRequest":{"properties":{"guest":{"type":"boolean","title":"Guest","default":false},"userId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Userid"},"anonId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Anonid"}},"type":"object","title":"QuotaRequest","description":"Same identity fields as ChatRequest so _daily_budget_for can resolve\nthe caller's budget. The Next.js proxy fills them from the server-side\nsession (userId for signed-in users, guest for everyone else); guests may\nalso carry the anonymous per-device id so the reported budget matches how\n/api/chat meters it (device-keyed, not pooled on a shared IP)."},"ShareCreateRequest":{"properties":{"source_chat_id":{"type":"string","title":"Source Chat Id"},"title":{"type":"string","title":"Title","default":""},"access_mode":{"type":"string","title":"Access Mode","default":"public"},"chat":{"additionalProperties":true,"type":"object","title":"Chat"}},"type":"object","required":["source_chat_id","chat"],"title":"ShareCreateRequest"},"ShareUpdateRequest":{"properties":{"access_mode":{"type":"string","title":"Access Mode","default":"public"}},"type":"object","title":"ShareUpdateRequest"},"UploadedFile":{"properties":{"name":{"type":"string","title":"Name","default":""},"size":{"type":"integer","title":"Size","default":0},"mimeType":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mimetype"},"data":{"type":"string","title":"Data","default":""}},"type":"object","title":"UploadedFile","description":"A file attached by the user. ``data`` is base64 (no data: URL prefix)."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"WorkspaceBrowseRequest":{"properties":{"userId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Userid"},"anonId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Anonid"},"path":{"type":"string","title":"Path","default":"."},"content":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content"},"overwrite":{"type":"boolean","title":"Overwrite","default":false},"recursive":{"type":"boolean","title":"Recursive","default":false},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"},"destination":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Destination"}},"type":"object","title":"WorkspaceBrowseRequest","description":"Identity + payload for the Files-panel endpoints.\n\nuserId/anonId mirror ChatRequest's fields — but unlike /api/chat, the\nNext.js proxy does NOT trust these from the body for signed-in users: it\ninjects X-ROK-User-Id from the Auth.js session and the backend prefers\nthat header (same trust model as the shares endpoints)."},"_V1ChatRequest":{"properties":{"model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model"},"messages":{"items":{"$ref":"#/components/schemas/_V1Message"},"type":"array","title":"Messages"},"stream":{"type":"boolean","title":"Stream","default":false},"temperature":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Temperature"},"top_p":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Top P"},"max_tokens":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Tokens"},"max_completion_tokens":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Completion Tokens"},"frequency_penalty":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Frequency Penalty"},"presence_penalty":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Presence Penalty"},"stop":{"anyOf":[{},{"type":"null"}],"title":"Stop"},"seed":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Seed"},"stream_options":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Stream Options"},"tools":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Tools"},"tool_choice":{"anyOf":[{},{"type":"null"}],"title":"Tool Choice"}},"type":"object","title":"_V1ChatRequest","description":"OpenAI chat.completions request body (including tool calling)."},"_V1Message":{"properties":{"role":{"type":"string","title":"Role"},"content":{"title":"Content","default":""},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"tool_call_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tool Call Id"},"tool_calls":{"anyOf":[{},{"type":"null"}],"title":"Tool Calls"}},"type":"object","required":["role"],"title":"_V1Message"},"_V1UsageRequest":{"properties":{"keyIds":{"items":{"type":"string"},"type":"array","title":"Keyids"},"accountId":{"type":"string","title":"Accountid","default":""}},"type":"object","title":"_V1UsageRequest","description":"What to report on: the caller's key row ids, and their account."}}}}