{"openapi":"3.1.0","info":{"title":"AICharli customer API","version":"1.1.1","summary":"HTTP contract for the same backend the live dashboard already calls","description":"Call the HTTP routes in this spec. They run the same server functions as the live dashboard.\nThe dashboard UI still uses Next.js server actions internally. Those hashes change every build; do not replay them.\n\n| Dashboard | HTTP |\n| --- | --- |\n| `login()` | `POST /api/v1/auth/login` |\n| `logout()` | `POST /api/v1/auth/logout` |\n| `createPodRequest()` | `POST /api/v1/assistants/claim` |\n| `getUserActiveAssignments()` / `getUnassignedPods()` / `getPodActivationStatus()` | `GET /api/v1/assistants` |\n| `upgradeToPrivate()` | `POST /api/v1/assistants/upgrade` |\n| `extendSession()` | `POST /api/v1/assistants/extend` |\n| `releaseMyPod()` | `POST /api/v1/assistants/release` |\n| Open assistant | `GET /api/agent-zero/launch` |\n| Resume | `POST /api/pods/{id}/resume` |\n| Proxy health | `GET /api/proxy/health` |\n\nSession cookie auth. Mutating `/api/v1` routes also need CSRF: `GET /api/csrf`, then `x-csrf-token`.\nDo not send GPU names or API keys. Chat is Agent Zero after launch; `POST /api/conversations` stores history only."},"servers":[{"url":"https://aicharli.com","description":"Production dashboard"},{"url":"http://127.0.0.1:3010","description":"Local dashboard"}],"tags":[{"name":"Auth","description":"CSRF, register, session"},{"name":"Assistants","description":"Shared vs Private claim, session, launch"},{"name":"History","description":"Conversation metadata stored on the account"},{"name":"Health","description":"Liveness and readiness"},{"name":"Inference","description":"Managed OpenAI-compatible proxy on api.aicharli.com"}],"paths":{"/api/csrf":{"get":{"tags":["Auth"],"operationId":"getCsrf","summary":"Issue a CSRF token","responses":{"200":{"description":"Token plus the header name to send on mutating requests","content":{"application/json":{"schema":{"type":"object","required":["token","headerName"],"properties":{"token":{"type":"string"},"headerName":{"type":"string","example":"x-csrf-token"}}}}}}}}},"/api/register":{"post":{"tags":["Auth"],"operationId":"register","summary":"Create an account","security":[{"csrf":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterRequest"}}}},"responses":{"201":{"description":"Pending or created, depending on approval settings"},"400":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"}}}},"/api/v1/auth/login":{"post":{"tags":["Auth"],"operationId":"login","summary":"Sign in and set the session cookie","description":"Same backend as dashboard server action login() in src/actions/auth.ts. The live login page calls the action; this route runs that function.","security":[{"csrf":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}}},"responses":{"200":{"description":"Session cookie set","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponse"}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"}}}},"/api/v1/auth/logout":{"post":{"tags":["Auth"],"operationId":"logout","summary":"Clear the session cookie","description":"Same backend as dashboard server action logout() in src/actions/auth.ts.","security":[{"session":[],"csrf":[]}],"responses":{"200":{"description":"Signed out","content":{"application/json":{"schema":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean"}}}}}}}}},"/api/v1/me":{"get":{"tags":["Auth"],"operationId":"getMe","summary":"Current account","security":[{"session":[]}],"responses":{"200":{"description":"Authenticated user","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Me"}}}},"401":{"$ref":"#/components/responses/Error"}}}},"/api/v1/assistants":{"get":{"tags":["Assistants"],"operationId":"getAssistants","summary":"Current assistant, plans, and credits","description":"Same backend as getUserActiveAssignments() and getUnassignedPods() in src/actions/dashboard-data.ts, plus getPodActivationStatus() via current.status. Poll until current.status is READY, then open current.launchPath.","security":[{"session":[]}],"responses":{"200":{"description":"Customer assistant snapshot","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssistantsSnapshot"}}}},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"}}}},"/api/v1/assistants/claim":{"post":{"tags":["Assistants"],"operationId":"claimAssistant","summary":"Claim Shared or Private","description":"Same backend as dashboard server action createPodRequest() in src/actions/requests.ts. The live claim modal calls the action with an internal pod id; this route picks the pod and then runs that function. Send only tier and durationHours.","security":[{"session":[],"csrf":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClaimRequest"}}}},"responses":{"200":{"description":"Claim accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClaimResponse"}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"}}}},"/api/v1/assistants/release":{"post":{"tags":["Assistants"],"operationId":"releaseAssistant","summary":"End the current session and refund unused time","description":"Same backend as dashboard server action releaseMyPod() in src/actions/pod-assignment.ts. Looks up the caller's assignment so the client does not send a pod id.","security":[{"session":[],"csrf":[]}],"responses":{"200":{"description":"Released","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReleaseResponse"}}}},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"}}}},"/api/v1/assistants/extend":{"post":{"tags":["Assistants"],"operationId":"extendAssistant","summary":"Add hours to the current session","description":"Same backend as dashboard server action extendSession() in src/actions/pod-assignment.ts.","security":[{"session":[],"csrf":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExtendRequest"}}}},"responses":{"200":{"description":"Extended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExtendResponse"}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"}}}},"/api/v1/assistants/upgrade":{"post":{"tags":["Assistants"],"operationId":"upgradeAssistant","summary":"Move Shared to Private on the same account","description":"Same backend as dashboard server action upgradeToPrivate() in src/actions/pod-assignment.ts. Memory stays on the account. Unused shared time is refunded first.","security":[{"session":[],"csrf":[]}],"responses":{"200":{"description":"Upgraded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClaimResponse"}}}},"401":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"}}}},"/api/agent-zero/launch":{"get":{"tags":["Assistants"],"operationId":"launchAssistant","summary":"Open the managed assistant (browser redirect)","description":"Same URL the live dashboard Open assistant button uses. Requires a session cookie. Returns 302 to the auto-login entrypoint when READY, or an HTML wait page while starting. Use from a top-level navigation or window.open, not as JSON.","security":[{"session":[]}],"responses":{"302":{"description":"Redirect to the assistant"},"403":{"description":"No active assignment"},"503":{"description":"Still starting"}}}},"/api/conversations":{"get":{"tags":["History"],"operationId":"listConversations","summary":"List account conversations","security":[{"session":[]}],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":30}}],"responses":{"200":{"description":"Conversation list"},"401":{"$ref":"#/components/responses/Error"}}},"post":{"tags":["History"],"operationId":"createConversation","summary":"Create a conversation record","description":"Does not start a model reply.","security":[{"session":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","maxLength":120}}}}}},"responses":{"201":{"description":"Created"},"401":{"$ref":"#/components/responses/Error"}}}},"/api/conversations/{id}/messages":{"get":{"tags":["History"],"operationId":"listMessages","summary":"List messages in a conversation","security":[{"session":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}}],"responses":{"200":{"description":"Messages"},"404":{"$ref":"#/components/responses/Error"}}},"post":{"tags":["History"],"operationId":"appendMessage","summary":"Store a message","description":"Persists history. It does not call the GPU model.","security":[{"session":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppendMessageRequest"}}}},"responses":{"201":{"description":"Stored"},"400":{"$ref":"#/components/responses/Error"}}}},"/api/proxy/health":{"get":{"tags":["Health"],"operationId":"proxyHealth","summary":"Auth-proxy health (dashboard poll)","description":"HTTP fetch used by the live dashboard ProxyStatusCard. Not a server action.","responses":{"200":{"description":"Proxy status JSON"}}}},"/api/pods/{id}/resume":{"post":{"tags":["Assistants"],"operationId":"resumeAssistantByPodId","summary":"Resume a stopped assistant runtime","description":"HTTP fetch used by the live dashboard user-pod-actions (POST /api/pods/{id}/resume). This is not a server action. A new frontend can keep using it if it has the assignment's internal id, or prefer polling GET /api/v1/assistants after claim.","security":[{"session":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Internal pod id. The live dashboard still sends this."}],"responses":{"200":{"description":"Resumed"},"202":{"description":"Resume in progress"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"}}}},"/api/user":{"get":{"tags":["Auth"],"operationId":"getUser","summary":"Current account (legacy path)","description":"Existing dashboard JSON route. Prefer GET /api/v1/me, which adds credits.","security":[{"session":[]}],"responses":{"200":{"description":"User"},"401":{"$ref":"#/components/responses/Error"}}}},"/api/health/live":{"get":{"tags":["Health"],"operationId":"live","summary":"Process liveness","responses":{"200":{"description":"Alive"}}}},"/api/health/ready":{"get":{"tags":["Health"],"operationId":"ready","summary":"Accepts traffic when the database is up","responses":{"200":{"description":"Ready"},"503":{"description":"Not ready"}}}},"/api/openapi.json":{"get":{"tags":["Health"],"operationId":"openapi","summary":"This OpenAPI document","responses":{"200":{"description":"OpenAPI 3.1 JSON"}}}},"/v1/chat/completions":{"servers":[{"url":"https://api.aicharli.com"}],"post":{"tags":["Inference"],"operationId":"chatCompletions","summary":"OpenAI-compatible chat completions","description":"Managed assistant inference. Authorization is a Bearer key minted for the claimed assistant, not a customer-supplied key. Prefer launchPath for the product chat UI.","security":[{"inferenceBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["messages"],"properties":{"model":{"type":"string"},"messages":{"type":"array","items":{"type":"object"}},"stream":{"type":"boolean","default":false}}}}}},"responses":{"200":{"description":"Completion or SSE stream"},"401":{"$ref":"#/components/responses/Error"}}}},"/v1/models":{"servers":[{"url":"https://api.aicharli.com"}],"get":{"tags":["Inference"],"operationId":"listModels","summary":"List managed models","security":[{"inferenceBearer":[]}],"responses":{"200":{"description":"Model list"}}}},"/v1/memory/search":{"servers":[{"url":"https://api.aicharli.com"}],"post":{"tags":["Inference"],"operationId":"searchMemory","summary":"Search the account's conversation memory","security":[{"inferenceBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["query"],"properties":{"query":{"type":"string"},"limit":{"type":"integer","minimum":1,"maximum":50,"default":5}}}}}},"responses":{"200":{"description":"Memory hits"}}}}},"components":{"securitySchemes":{"session":{"type":"apiKey","in":"cookie","name":"session","description":"HTTP-only JWT session cookie from login"},"csrf":{"type":"apiKey","in":"header","name":"x-csrf-token","description":"Token from GET /api/csrf"},"inferenceBearer":{"type":"http","scheme":"bearer","description":"Managed key for api.aicharli.com. Not customer-supplied."}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}},"LoginRequest":{"type":"object","required":["username","password"],"properties":{"username":{"type":"string","description":"Username or email"},"password":{"type":"string"}}},"LoginResponse":{"type":"object","required":["success","role"],"properties":{"success":{"type":"boolean"},"role":{"type":"string","enum":["USER","ADMIN"]}}},"RegisterRequest":{"type":"object","required":["username","email","password"],"properties":{"username":{"type":"string"},"email":{"type":"string","format":"email"},"password":{"type":"string","minLength":8}}},"Me":{"type":"object","required":["id","username","role","credits"],"properties":{"id":{"type":"string"},"username":{"type":"string"},"email":{"type":["string","null"]},"role":{"type":"string","enum":["USER","ADMIN"]},"credits":{"type":"number"}}},"Plan":{"type":"object","required":["tier","available","creditRate"],"properties":{"tier":{"type":"string","enum":["PRIVATE","SHARED"]},"available":{"type":"boolean"},"creditRate":{"type":"number"}}},"Assistant":{"type":"object","required":["assignmentId","tier","status","creditRate","expiresAt","durationHours","launchPath"],"properties":{"assignmentId":{"type":"string"},"tier":{"type":"string","enum":["PRIVATE","SHARED"]},"status":{"type":"string","enum":["READY","STARTING"]},"creditRate":{"type":"number"},"expiresAt":{"type":["string","null"],"format":"date-time"},"durationHours":{"type":["integer","null"]},"launchPath":{"type":"string","example":"/api/agent-zero/launch"}}},"AssistantsSnapshot":{"type":"object","required":["credits","plans","current"],"properties":{"credits":{"type":"number"},"plans":{"type":"array","items":{"$ref":"#/components/schemas/Plan"}},"current":{"oneOf":[{"$ref":"#/components/schemas/Assistant"},{"type":"null"}]}}},"ClaimRequest":{"type":"object","required":["tier","durationHours"],"properties":{"tier":{"type":"string","enum":["PRIVATE","SHARED"]},"durationHours":{"type":"integer","minimum":1,"maximum":168}}},"ClaimResponse":{"type":"object","required":["success","assistant"],"properties":{"success":{"type":"boolean"},"assistant":{"$ref":"#/components/schemas/Assistant"},"remainingCredits":{"type":"number"},"message":{"type":"string"}}},"ExtendRequest":{"type":"object","required":["durationHours"],"properties":{"durationHours":{"type":"integer","minimum":1,"maximum":168}}},"ExtendResponse":{"type":"object","required":["success","remainingCredits","expiresAt","durationHours"],"properties":{"success":{"type":"boolean"},"remainingCredits":{"type":"number"},"expiresAt":{"type":["string","null"],"format":"date-time"},"durationHours":{"type":"integer"}}},"ReleaseResponse":{"type":"object","required":["success","refunded"],"properties":{"success":{"type":"boolean"},"refunded":{"type":"number"}}},"AppendMessageRequest":{"type":"object","required":["role","content"],"properties":{"role":{"type":"string","enum":["USER","ASSISTANT","SYSTEM"]},"content":{"type":"string","maxLength":32000}}}},"responses":{"Error":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}