Разработчикам

API Reference

This document describes Milly Lab's HTTP and WebSocket API. It is for developers integrating with Milly Lab programmatically.


#Base URL

https://aria-web-production-a38d.up.railway.app

All endpoints are prefixed /api.

Interactive documentation

Swagger UI (/docs) and ReDoc (/redoc) are disabled in production. They are available only when the application runs outside a production environment.

To obtain the OpenAPI schema, run the application locally with APP_ENV=development and fetch /openapi.json.


#Authentication

Milly Lab uses JWT bearer tokens.

TokenLifetime
Access token60 minutes
Refresh token30 days

Obtaining tokens

POST /api/auth/login
Content-Type: application/json

{"email": "user@example.com", "password": "your-password"}

The response contains an access token, a refresh token, and the user record. Send the access token on every subsequent request:

Authorization: Bearer <access_token>

Refreshing

POST /api/auth/refresh
Content-Type: application/json

{"refresh_token": "<refresh_token>"}

Returns a new token pair.

Revocation

Tokens carry the account's revocation version. POST /api/auth/logout, a password change, and a password reset all invalidate every token issued earlier. A revoked token returns 401; obtain a new pair by signing in again.


#Error handling

Errors use standard HTTP status codes with a JSON body.

StatusMeaning
400Malformed request
401Missing, invalid, expired or revoked token
402Payment required — see below
403Authenticated but not permitted
404Not found, or not owned by the caller
409Conflict — a competing operation is in progress
422Validation failure
429Rate limited
500Server error

Most errors return {"detail": "..."}. 402 responses return a structured object so clients can branch on the cause.

The payment-required family

{
  "detail": {
    "code": "insufficient_balance",
    "model_key": "claude-sonnet-5",
    "plan": "pro",
    "balance_usd": 0.42,
    "needed_usd": 1.20,
    "message": "Monthly allowance for this model is exhausted and your top-up balance is too low. Add funds to continue."
  }
}
codeCauseAdditional fields
free_limitFree tier per-model window exhaustedlimit, window_hours, used, reset_at
free_model_lockedModel not available on the free tierfree_models
plan_requiredFeature requires any paid planfeature
feature_requiredPlan lacks this capabilityfeature, plan
insufficient_balanceAllowance spent, balance too lowbalance_usd, needed_usd
insufficient_for_requestThis request exceeds what remainsneeded_usd, afford_usd, balance_usd

Always branch on code, never on message — messages are user-facing and may change.


#Rate limits

Applied per client IP by category.

CategoryLimit
Authentication endpoints100 requests per minute
All other endpoints1,000 requests per minute

Rate limiting is backed by Redis. When Redis is unavailable, authentication endpoints fall back to a per-process in-memory limiter so they are never entirely unlimited; other traffic passes through.

Exceeding a limit returns 429.


#Endpoints

Authentication — /api/auth

MethodPathPurpose
POST/signupCreate an account
POST/loginSign in
POST/googleSign in with a Google ID token
POST/refreshExchange a refresh token for a new pair
POST/logoutRevoke the current session
GET/meThe authenticated user
POST/verify-emailVerify with an emailed token
POST/forgot-passwordRequest a reset email
POST/reset-passwordReset using an emailed token
POST/reset-password/verifyCheck a reset token is valid
POST/change-passwordChange password while signed in

Users — /api/users

MethodPathPurpose
GET/meProfile
PATCH/meUpdate profile
GET/me/usageUsage records
GET/me/entitlementsPlan, per-model allowance remaining, feature flags
DELETE/meDeactivates the account; data removal is by request — see the Privacy Policy

GET /me/entitlements is the endpoint to call before offering a model in a UI — it reports what the caller may actually use.

Conversations — /api/conversations

MethodPathPurpose
GET``List
POST``Create
GET/{id}Detail with messages
PATCH/{id}Rename, move to a space
DELETE/{id}Delete
POST/{id}/messagesSend a message — streams the response
GET/shared/{share_token}Read a shared conversation without authentication

POST /{id}/messages is the main chat endpoint. It streams the response. The 402 gate is applied before any provider call, so a payment-required error arrives instead of a stream, not partway through one.

A full event-by-event description of the streaming format is not yet published; the response streams as it is generated and completes with the final usage totals.

Billing — /api/billing

MethodPathPurpose
GET/plansAvailable plans
GET/subscriptionCurrent subscription
GET/balancePrepaid balance
GET/walletBalance with allowance detail
GET/transactionsTransaction history
POST/topupAdd balance
POST/upgradeChange plan

/topup and /upgrade exist but no payment gateway is connected. They do not take payment.

Swarm Mind — Project Mode — /api/project

MethodPathPurpose
POST/sessionsStart a run
GET/sessionsList
GET/sessions/{id}Detail
POST/sessions/{id}/controlPause, resume, cancel
POST/sessions/{id}/messageSend a message into a live run
POST/sessions/{id}/reassignReassign a subtask to another model
POST/sessions/{id}/replaceReplace a subtask result
GET/sessions/{id}/filesFiles visible to the run
GET/sessions/{id}/deliverablesProduced deliverables
GET/deliverables/{id}/downloadDownload one
GET/sessions/{id}/eventsReplay events — see below
WS/api/project/ws/{id}Live event stream

Concurrency: a cap applies to simultaneous runs per user. Exceeding it returns an error rather than queueing.

Connected workspaces — /api/project/workspaces

MethodPathPurpose
POST/workspacesCreate a workspace binding
GET/workspacesList
DELETE/workspaces/{id}Disconnect
GET/workspaces/{id}/reposRepositories available on the connection
GET/sessions/{id}/changesStaged changes — nothing written yet
POST/sessions/{id}/changes/applyWrite staged changes to the real workspace
POST/sessions/{id}/changes/discardDiscard staged changes

GET /sessions/{id}/changes:

{
  "files": [{"path": "src/app.ts", "op": "edit", "content": "..."}],
  "proposal": {"target": "branch", "branch": "aria/refactor", "message": "..."},
  "applied": null
}

POST /sessions/{id}/changes/apply:

{
  "target": "branch",
  "branch": "aria/refactor",
  "message": "Refactor the request handler"
}
  • target is "default" or "branch". Empty falls back to the run's own proposal.
  • 409 if a run is still executing, or if an apply is already in progress. The in-progress claim self-expires after 120 seconds.
  • 400 if there are no staged changes.
  • 404 if the workspace connection no longer exists.
  • Per-file failures are reported individually; the apply is not all-or-nothing.

Swarm Mind — Council Mode — /api/council

MethodPathPurpose
POST/sessionsStart a council
GET/sessionsList
GET/sessions/{id}Detail
POST/sessions/{id}/cardsAdd a model's card
POST/cards/{id}/regenerateRegenerate a card
DELETE/cards/{id}Remove a card
GET/cards/{id}/diffDiff against another card
GET/cards/{id}/versionsVersion history
POST/cards/{id}/restoreRestore a version
GET/cards/{id}/exportExport
POST/cards/{id}/messagesMessage a single card
GET/cards/{id}/messagesCard conversation
POST/sessions/{id}/chatMessage the whole council
POST/sessions/{id}/mergeMerge cards into a final
POST/sessions/{id}/debateRun a debate round
GET/sessions/{id}/eventsReplay events
WS/api/council/ws/{id}Live event stream

Studios

Images — /api/images

MethodPathPurpose
POST``Generate
GET``List jobs
DELETE/{job_id}Delete
POST/{job_id}/starStar

Video — /api/videos

MethodPathPurpose
POST``Generate
GET``List jobs
DELETE/{job_id}Delete
POST/{job_id}/starStar
POST/{job_id}/extendExtend a finished clip

3D — /api/assets-3d

MethodPathPurpose
POST/generateGenerate
GET/assetsList
GET/assets/{id}Detail
GET/assets/{id}/statusJob status

Generation is asynchronous: the POST returns a job, and you poll status or watch the job list. A budget hold is placed when the job is queued and settled on completion.

Job status — GET /api/jobs/{job_id} for any generation job.

Teams — /api/teams

MethodPathPurpose
GET/meYour team
POST/meCreate
POST/me/invitesInvite by email
POST/invites/{token}/acceptAccept an invitation
POST/me/leaveLeave
POST/me/transfer-ownershipTransfer ownership
PATCH/me/members/{id}Change a member's role
DELETE/me/members/{id}Remove a member

Inviting sends an email. No account or membership is created until the invitation is accepted, so an invitation cannot be used to probe whether an address is registered.

Other

MethodPathPurpose
GET/POST/PATCH/DELETE/api/spacesSpaces
POST/api/uploadsUpload a file
GET/api/files/{message_id}Files attached to a message
POST/api/transcribeSpeech to text
GET/api/flagsFeature flags (authenticated)
GET/api/cmsMarketing content
GET/PATCH/POST/api/agentsPer-model preferences
GET/api/connectors/providersAvailable connector providers
GET/api/connectors/{provider}/authorizeBegin OAuth
GET/api/connectors/{provider}/callbackOAuth callback

Administrative endpoints under /api/admin require a platform-operator account and are out of scope here.


#WebSocket protocol

WS /api/project/ws/{session_id}
WS /api/council/ws/{session_id}

Authenticate at connection time by passing your access token as a ?token=<access_token> query parameter, or as an Authorization: Bearer header on the upgrade request.

Close codes:

CodeMeaning
4403The session does not belong to you

The server sends {"type":"ping"} roughly every 20 seconds. Clients should tolerate it and may respond with any text frame to keep the connection alive.

Events are self-describing JSON objects with a type field. The replay endpoint returns exactly what the socket delivers, so the two sources never disagree.

Replay — never lose state on reconnect

Events are persisted to an append-only log with a monotonic sequence number. A client that disconnects catches up rather than resyncing from scratch:

GET /api/project/sessions/{id}/events?after_seq=421

Returns a bare JSON array of events with seq > after_seq.

  • The page is capped server-side. limit can only make a page smaller, never larger.
  • The response header X-Next-Seq carries the sequence to pass as after_seq on the next call.
  • Page forward until the array comes back empty, then rely on the socket.

The recommended client pattern is: replay from the last applied sequence, open the socket, then replay again from the last applied sequence to close the gap.


#Pagination

Pagination is not uniform across the API.

  • Event replay uses after_seq with the X-Next-Seq header.
  • List endpoints generally return a bounded collection; where paging parameters exist, they can only narrow the server's own bounds, never widen them.

#Versioning

The API does not currently carry a version in its path.