Frontend contract

AICharli customer API

HTTP routes for a custom frontend. They run the same server functions as the live dashboard.

Contents

Authentication

  • Log in with POST /api/v1/auth/login. The session is an HTTP-only cookie, so send cookies with every request.
  • Login, register, logout and the mutating /api/v1/assistants routes also need CSRF: call GET /api/csrf (it sets a cookie too) and send the returned token in x-csrf-token.
  • The /api/conversations routes need the session cookie only.
  • Each endpoint below lists what it needs. Do not send GPU names or API keys.

Typical flow

  1. GET /api/csrf, then POST /api/v1/auth/login with x-csrf-token.
  2. GET /api/v1/assistants and show Shared vs Private from plans.
  3. POST /api/v1/assistants/claim with tier and durationHours only. Allow at least 180 s.
  4. Poll GET /api/v1/assistants until current.status is READY.
  5. Open current.launchPath for the managed assistant.
  6. Keep history with POST /api/conversations and POST /api/conversations/{id}/messages; these store messages and do not call the model.
  7. POST /api/v1/assistants/release to end the session early and get unused time refunded.

Auth

CSRF, register, session

GET/api/csrf

Issue a CSRF token

No auth
Response 200Token plus the header name to send on mutating requests
FieldTypeNotes
tokenrequiredstring
headerNamerequiredstring
POST/api/register

Create an account

CSRF header
Request bodyJSON, required, RegisterRequest
FieldTypeNotes
usernamerequiredstring
emailrequiredstringemail
passwordrequiredstringmin length 8
acceptTermsbooleanAcceptance of the Terms of Service and Privacy Policy. Web sign-ups require it and always send true; when true the acceptance time is stored. Optional for API clients.
Response 201Account created and active; log in straight away.
FieldTypeNotes
successrequiredboolean
messagerequiredstring

Errors:400403409429503with an error body

POST/api/v1/auth/login

Sign in and set the session cookie

CSRF header

Same code as the dashboard's login() function. Checks the username or email and password and sets the session cookie.

Request bodyJSON, required, LoginRequest
FieldTypeNotes
usernamerequiredstringUsername or email
passwordrequiredstring
Response 200Session cookie set, LoginResponse
FieldTypeNotes
successrequiredboolean
rolerequiredstringOne of USER, ADMIN

Errors:400401403with an error body

POST/api/v1/auth/password-reset/request

Ask for a password reset email

CSRF header

Send email or username. The answer is always the same 200, whether or not an account matches, and mail is only sent when the server has SMTP configured. The link in the mail is valid for 1 hour. Limited to 10 requests per hour per IP and 3 per hour per identifier.

Request bodyJSON, required, PasswordResetRequest
FieldTypeNotes
emailstring
usernamestring
Response 200Request accepted
FieldTypeNotes
successrequiredboolean
messagerequiredstring

Errors:400403429with an error body

POST/api/v1/auth/password-reset/confirm

Set a new password with a reset token

CSRF header

Send the token from the reset link and the new password. A successful reset signs out every older session.

Request bodyJSON, required, PasswordResetConfirm
FieldTypeNotes
tokenrequiredstring
passwordrequiredstringmin length 8
Response 200Password changed
FieldTypeNotes
successrequiredboolean
messagerequiredstring

Errors:400403500with an error body

POST/api/v1/auth/verify-email

Verify the email address with a token

CSRF header

Send the token from the verification link. A token works once and is valid for 24 hours.

Request bodyJSON, required, VerifyEmailRequest
FieldTypeNotes
tokenrequiredstring
Response 200Email verified
FieldTypeNotes
successrequiredboolean
messagerequiredstring

Errors:400403500with an error body

POST/api/v1/auth/verify-email/resend

Send a new verification email

Session cookieCSRF header

Sends a new verification link to the signed-in user when their email still needs verification. Limited to 3 per hour per user.

Response 200Request accepted
FieldTypeNotes
successrequiredboolean
messagerequiredstring

Errors:401403429with an error body

POST/api/v1/auth/logout

Clear the session cookie

Session cookieCSRF header

Clears the session cookie.

Response 200Signed out
FieldTypeNotes
successrequiredboolean
GET/api/v1/me

Current account

Session cookie
Response 200Authenticated user, Me
FieldTypeNotes
idrequiredstring
usernamerequiredstring
emailstring | null
rolerequiredstringOne of USER, ADMIN
creditsrequirednumber
emailVerifiedrequiredboolean

Errors:401with an error body

GET/api/user

Current account (legacy path)

Session cookie

Existing dashboard JSON route. Prefer GET /api/v1/me, which adds credits.

Response 200User

Errors:401with an error body

Assistants

Shared vs Private claim, session, launch

GET/api/v1/assistants

Current assistant, plans, and credits

Session cookie

Returns the plans you can claim and your current assistant, if any. Poll until current.status is READY, then open current.launchPath. READY always includes a non-null expiresAt; while the pod is healthy but the billing clock has not started yet, status stays STARTING and expiresAt stays null. While STARTING, current.provisioning reports the pod's own status, when it was claimed, and warmupDeadlineAt, the point by which the claim is fully refunded and the pod released if it still has not come up. Do not call release or upgrade while current.status is STARTING before provisioning.warmupDeadlineAt. FAILED is terminal: the claim could not be set up and error says why. The charge is refunded automatically; a refund that fails is retried every monitoring cycle until it succeeds. It stays visible for about 10 minutes, then current becomes null.

Response 200Customer assistant snapshot, AssistantsSnapshot
FieldTypeNotes
creditsrequirednumber
plansrequiredPlan[]
plans[].tierrequiredstringOne of PRIVATE, SHARED
plans[].availablerequiredboolean
plans[].creditRaterequirednumber
currentrequiredobject

Errors:401403with an error body

POST/api/v1/assistants/claim

Claim Shared or Private

Session cookieCSRF header

The server picks a free assistant of the requested tier. Send only tier and durationHours. The request returns at once with status STARTING once the charge and assignment commit; the workspace is prepared in the background. Poll GET /api/v1/assistants until current.status is READY (or FAILED, whose charge is refunded automatically). Do not retry blindly: retrying a claim that already succeeded returns 409 with a clear already-held message and never charges twice.

Request bodyJSON, required, ClaimRequest
FieldTypeNotes
tierrequiredstringOne of PRIVATE, SHARED
durationHoursrequiredintegermin 1. max 168
Response 200Claim accepted, ClaimResponse
FieldTypeNotes
successrequiredboolean
assistantrequiredAssistant
assistant.assignmentIdrequiredstring
assistant.tierrequiredstringOne of PRIVATE, SHARED
assistant.statusrequiredstringOne of READY, STARTING, FAILED
assistant.creditRaterequirednumber
assistant.expiresAtrequiredstring | nulldate-time. Session end time stamped by the billing worker. Always non-null when status is READY; null while STARTING.
assistant.durationHoursrequiredinteger | null
assistant.launchPathrequiredstring
assistant.errorstringPresent only when status is FAILED
assistant.provisioningrequiredobject | nullSet while status is STARTING and the billing clock has not started, including a RUNNING pod still waiting on its first billing tick; null once expiresAt is set.
assistant.provisioning.podStatusrequiredstringOne of IDLE, ERROR, PENDING, HIBERNATED, RUNNING
assistant.provisioning.claimedAtrequiredstringdate-time
assistant.provisioning.warmupDeadlineAtrequiredstringdate-time
remainingCreditsnumber
messagestring

Errors:400401403409503with an error body

POST/api/v1/assistants/release

End the current session and refund unused time

Session cookieCSRF header

Ends the caller's current session and refunds unused time. The server finds the session, so the client sends no id.

Response 200Released, ReleaseResponse
FieldTypeNotes
successrequiredboolean
refundedrequirednumber

Errors:401403409with an error body

POST/api/v1/assistants/extend

Add hours to the current session

Session cookieCSRF header

Adds hours to the caller's current session and charges credits for them.

Request bodyJSON, required, ExtendRequest
FieldTypeNotes
durationHoursrequiredintegermin 1. max 168
Response 200Extended, ExtendResponse
FieldTypeNotes
successrequiredboolean
remainingCreditsrequirednumber
expiresAtrequiredstring | nulldate-time
durationHoursrequiredinteger

Errors:400401409with an error body

POST/api/v1/assistants/upgrade

Move Shared to Private on the same account

Session cookieCSRF header

Memory stays on the account. Unused shared time is refunded first. The request can take up to ~3 minutes while the private pod is prepared: use a client timeout of at least 180 s. If your client times out anyway, the upgrade still completes; do not retry blindly, instead GET /api/v1/assistants to read the resulting assignment.

Response 200Upgraded, ClaimResponse
FieldTypeNotes
successrequiredboolean
assistantrequiredAssistant
assistant.assignmentIdrequiredstring
assistant.tierrequiredstringOne of PRIVATE, SHARED
assistant.statusrequiredstringOne of READY, STARTING, FAILED
assistant.creditRaterequirednumber
assistant.expiresAtrequiredstring | nulldate-time. Session end time stamped by the billing worker. Always non-null when status is READY; null while STARTING.
assistant.durationHoursrequiredinteger | null
assistant.launchPathrequiredstring
assistant.errorstringPresent only when status is FAILED
assistant.provisioningrequiredobject | nullSet while status is STARTING and the billing clock has not started, including a RUNNING pod still waiting on its first billing tick; null once expiresAt is set.
assistant.provisioning.podStatusrequiredstringOne of IDLE, ERROR, PENDING, HIBERNATED, RUNNING
assistant.provisioning.claimedAtrequiredstringdate-time
assistant.provisioning.warmupDeadlineAtrequiredstringdate-time
remainingCreditsnumber
messagestring

Errors:401409503with an error body

POST/api/v1/assistants/chat

Chat with your assistant

Session cookie

Sends messages to your own READY assistant and returns the answer. Needs the session cookie and the x-csrf-token header; the server holds the inference key, so none is ever sent to or returned to the browser. Set stream to true for token-by-token OpenAI-style server-sent events (data: lines ending with data: [DONE]); otherwise one JSON chat completion is returned. Any model, tools or other field you send is ignored: the model is fixed to your assistant's. The inference service raises max_tokens below 4096 to 4096. Errors before the answer starts carry error and code; 409 means no assistant or one that is not READY yet (poll GET /api/v1/assistants), 429 includes Retry-After, 503 is retryable. If a stream breaks after it started, a final data event with an error object and data: [DONE] close it. Closing the connection cancels the request. Nothing is stored; use POST /api/conversations/{id}/messages for history.

Request bodyJSON, required, AssistantChatRequest
FieldTypeNotes
messagesrequiredobject[]
messages[].rolerequiredstringOne of system, user, assistant
messages[].contentrequiredstringmax length 32000
streambooleanDefault false. true returns server-sent events.
max_tokensintegermin 1. max 32768. The inference service raises values below 4096 to 4096.
temperaturenumbermin 0. max 2
Response 200Chat completion, or an event stream when stream is true

Errors:400401402403409429502503504with an error body

GET/api/agent-zero/launch

Open the managed assistant (browser redirect)

Session cookie

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.

Response 302Redirect to the assistant

Errors:403503with an error body

POST/api/pods/{id}/resume

Resume a stopped assistant runtime

Session cookie

Used by the built-in dashboard. {id} is the internal pod id, which the customer API does not return, so a custom frontend should not call this; poll GET /api/v1/assistants after claim instead.

Parameters
FieldTypeNotes
id (path)requiredstringInternal pod id. The live dashboard still sends this.
Response 200Resumed
Response 202Resume in progress

Errors:401403with an error body

History

Conversation history stored on the account. Works without an active assistant, does not need CSRF, and is readable after a session ends. File upload is not available to customers yet.

GET/api/conversations

List account conversations

Session cookie

Ordered by updatedAt, newest first, so the conversation with the latest message comes first. updatedAt moves when a message is added (conversations that have had no message since they were created keep their creation time). Returns at most limit (up to 100) conversations, with no paging past that. Metadata only; read a conversation's messages with GET /api/conversations/{id}/messages. Delete one with DELETE /api/conversations/{id}. Admin accounts get 403.

Parameters
FieldTypeNotes
limit (query)integermin 1. max 100. default 30
Response 200Conversation list, ConversationList
FieldTypeNotes
conversationsrequiredConversationSummary[]
conversations[].idrequiredstring
conversations[].titlerequiredstring | null
conversations[].createdAtrequiredstringdate-time
conversations[].updatedAtrequiredstringdate-time
conversations[].messageCountrequiredinteger

Errors:401403500with an error body

POST/api/conversations

Create a conversation record

Session cookie

Does not start a model reply. A JSON body is required; send {} to create a conversation without a title, which is stored as null. There is no rename yet.

Request bodyJSON, required
FieldTypeNotes
titlestringmax length 120
Response 201Created, Conversation
FieldTypeNotes
idrequiredstring
titlerequiredstring | null
createdAtrequiredstringdate-time
updatedAtrequiredstringdate-time

Errors:400401403500with an error body

GET/api/conversations/{id}/messages

List messages in a conversation

Session cookie

Newest first: the first message in the list is the latest. Returns the newest limit messages (up to 200); pass order=asc to list them oldest first instead. hasMore is true when older messages exist, and nextBefore is then the id to pass as before to load the next older page (it is null on the last page). before returns the limit messages just before that message, in the same order setting. An unknown before id, or an order other than asc or desc, gets 400. Each message lists its attached files and generated artifacts.

Parameters
FieldTypeNotes
id (path)requiredstring
limit (query)integermin 1. max 200. default 50
before (query)stringId of a message in this conversation. Returns the messages just before it. Use nextBefore from the previous response.
order (query)stringOrder of messages inside the page: desc lists the newest first (default), asc the oldest first.. One of desc, asc. default desc
Response 200Messages, MessageList
FieldTypeNotes
messagesrequiredMessage[]
messages[].idrequiredstring
messages[].rolerequiredstringOne of SYSTEM, USER, ASSISTANT, TOOL
messages[].contentrequiredstring
messages[].podIdrequiredstring | null
messages[].createdAtrequiredstringdate-time
messages[].uploadedFilesrequiredMessageFile[]
messages[].uploadedFiles[].idrequiredstring
messages[].uploadedFiles[].originalNamerequiredstring
messages[].uploadedFiles[].mimeTyperequiredstring
messages[].uploadedFiles[].sizeBytesrequiredinteger
messages[].uploadedFiles[].statusrequiredstringOne of PENDING, AVAILABLE, FAILED, DELETED
messages[].artifactsrequiredMessageArtifact[]
messages[].artifacts[].idrequiredstring
messages[].artifacts[].typerequiredstringOne of TEXT, JSON, IMAGE, FILE
messages[].artifacts[].mimeTyperequiredstring
messages[].artifacts[].sizeBytesrequiredinteger
messages[].artifacts[].createdAtrequiredstringdate-time
hasMorerequiredbooleanTrue when older messages exist beyond this page.
nextBeforerequiredstring | nullId to pass as before to load the next older page; null when hasMore is false.

Errors:400401403404with an error body

POST/api/conversations/{id}/messages

Store a message

Session cookie

Persists history. It does not call the GPU model. content is trimmed before it is stored and must not be empty after trimming. Leave podId and uploadedFileIds out: the customer API exposes neither pod ids nor file upload yet; both are only used by the built-in dashboard. 403 covers admin accounts, a pod that is not assigned to the caller, and unavailable files.

Parameters
FieldTypeNotes
id (path)requiredstring
Request bodyJSON, required, AppendMessageRequest
FieldTypeNotes
rolerequiredstringOne of SYSTEM, USER, ASSISTANT, TOOL
contentrequiredstringmin length 1. max length 32000
podIdstringInternal; customers leave it out.
uploadedFileIdsstring[]Internal; customers leave it out.
Response 201Stored, StoredMessage
FieldTypeNotes
idrequiredstring
rolerequiredstringOne of SYSTEM, USER, ASSISTANT, TOOL
contentrequiredstring
podIdrequiredstring | null
createdAtrequiredstringdate-time

Errors:400401403404with an error body

DELETE/api/conversations/{id}

Delete a conversation

Session cookie

Deletes one of your conversations together with its messages. Files you attached stay available but are no longer linked to it. 404 covers an unknown id and a conversation that belongs to someone else. Admin accounts get 403.

Parameters
FieldTypeNotes
id (path)requiredstring
Response 200Deleted, DeleteConversationResponse
FieldTypeNotes
successrequiredboolean

Errors:401403404500with an error body

Billing

Credit top-ups through Stripe Checkout and payment history

POST/api/v1/billing/checkout

Start a credit top-up

Session cookieCSRF header

Creates a Stripe Checkout session for one credit pack and returns its url; send the customer there to pay. Credits are added once Stripe confirms the payment, never from the redirect back. Answers 503 with code PAYMENTS_DISABLED until payments are enabled, and 403 for admin accounts and for unverified email addresses when verification is required.

Request bodyJSON, required, CheckoutRequest
FieldTypeNotes
amountrequiredintegerPack size in currency units; one of BillingOverview.amounts
Response 200Checkout session created
FieldTypeNotes
urlrequiredstring

Errors:400401403429500502503with an error body

GET/api/v1/billing/payments

Top-up packs and payment history

Session cookie

Returns whether payments are enabled, the pack amounts you can buy, the currency, and your 50 most recent payments, newest first.

Response 200Billing overview, BillingOverview
FieldTypeNotes
enabledrequiredboolean
amountsrequiredinteger[]
currencyrequiredstring
paymentsrequiredPayment[]
payments[].idrequiredstring
payments[].kindrequiredstringOne of TOPUP, SUBSCRIPTION
payments[].statusrequiredstringOne of PENDING, PAID, FAILED, EXPIRED, REFUNDED
payments[].amountrequirednumberPrice in currency units
payments[].currencyrequiredstring
payments[].creditsrequirednumber
payments[].createdAtrequiredstringdate-time
payments[].paidAtrequiredstring | nulldate-time

Errors:401with an error body

Health

Liveness and readiness

GET/api/proxy/health

Auth-proxy health (dashboard poll)

No auth

Reports whether the inference proxy is reachable.

Response 200Proxy status JSON
GET/api/health/live

Process liveness

No auth
Response 200Alive
GET/api/health/ready

Accepts traffic when the database is up

No auth
Response 200Ready

Errors:503with an error body

GET/api/openapi.json

This OpenAPI document

No auth
Response 200OpenAPI 3.1 JSON

Inference

Managed OpenAI-compatible proxy on api.aicharli.com

POST/v1/chat/completionson https://api.aicharli.com

OpenAI-compatible chat completions

Bearer key

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.

Request bodyJSON, required
FieldTypeNotes
modelstring
messagesrequiredobject[]
streambooleandefault false
Response 200Completion or SSE stream

Errors:401with an error body

GET/v1/modelson https://api.aicharli.com

List managed models

Bearer key
Response 200Model list
POST/v1/memory/searchon https://api.aicharli.com

Search the account's conversation memory

Bearer key
Request bodyJSON, required
FieldTypeNotes
queryrequiredstring
limitintegermin 1. max 50. default 5
Response 200Memory hits

Error body

error is always present. code and retryable appear only on errors that define them (for example GPU_ADMISSION_CLOSED and CLAIM_IN_PROGRESS); a plain 401 is {"error":"Unauthorized"}.

FieldTypeNotes
errorrequiredstring
codestring
retryableboolean