Барномасозон
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.
| Token | Lifetime |
|---|---|
| Access token | 60 minutes |
| Refresh token | 30 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.
| Status | Meaning |
|---|---|
| 400 | Malformed request |
| 401 | Missing, invalid, expired or revoked token |
| 402 | Payment required — see below |
| 403 | Authenticated but not permitted |
| 404 | Not found, or not owned by the caller |
| 409 | Conflict — a competing operation is in progress |
| 422 | Validation failure |
| 429 | Rate limited |
| 500 | Server 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."
}
}
code | Cause | Additional fields |
|---|---|---|
free_limit | Free tier per-model window exhausted | limit, window_hours, used, reset_at |
free_model_locked | Model not available on the free tier | free_models |
plan_required | Feature requires any paid plan | feature |
feature_required | Plan lacks this capability | feature, plan |
insufficient_balance | Allowance spent, balance too low | balance_usd, needed_usd |
insufficient_for_request | This request exceeds what remains | needed_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.
| Category | Limit |
|---|---|
| Authentication endpoints | 100 requests per minute |
| All other endpoints | 1,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
| Method | Path | Purpose |
|---|---|---|
| POST | /signup | Create an account |
| POST | /login | Sign in |
| POST | /google | Sign in with a Google ID token |
| POST | /refresh | Exchange a refresh token for a new pair |
| POST | /logout | Revoke the current session |
| GET | /me | The authenticated user |
| POST | /verify-email | Verify with an emailed token |
| POST | /forgot-password | Request a reset email |
| POST | /reset-password | Reset using an emailed token |
| POST | /reset-password/verify | Check a reset token is valid |
| POST | /change-password | Change password while signed in |
Users — /api/users
| Method | Path | Purpose |
|---|---|---|
| GET | /me | Profile |
| PATCH | /me | Update profile |
| GET | /me/usage | Usage records |
| GET | /me/entitlements | Plan, per-model allowance remaining, feature flags |
| DELETE | /me | Deactivates 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
| Method | Path | Purpose |
|---|---|---|
| GET | `` | List |
| POST | `` | Create |
| GET | /{id} | Detail with messages |
| PATCH | /{id} | Rename, move to a space |
| DELETE | /{id} | Delete |
| POST | /{id}/messages | Send 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
| Method | Path | Purpose |
|---|---|---|
| GET | /plans | Available plans |
| GET | /subscription | Current subscription |
| GET | /balance | Prepaid balance |
| GET | /wallet | Balance with allowance detail |
| GET | /transactions | Transaction history |
| POST | /topup | Add balance |
| POST | /upgrade | Change plan |
/topupand/upgradeexist but no payment gateway is connected. They do not take payment.
Swarm Mind — Project Mode — /api/project
| Method | Path | Purpose |
|---|---|---|
| POST | /sessions | Start a run |
| GET | /sessions | List |
| GET | /sessions/{id} | Detail |
| POST | /sessions/{id}/control | Pause, resume, cancel |
| POST | /sessions/{id}/message | Send a message into a live run |
| POST | /sessions/{id}/reassign | Reassign a subtask to another model |
| POST | /sessions/{id}/replace | Replace a subtask result |
| GET | /sessions/{id}/files | Files visible to the run |
| GET | /sessions/{id}/deliverables | Produced deliverables |
| GET | /deliverables/{id}/download | Download one |
| GET | /sessions/{id}/events | Replay 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
| Method | Path | Purpose |
|---|---|---|
| POST | /workspaces | Create a workspace binding |
| GET | /workspaces | List |
| DELETE | /workspaces/{id} | Disconnect |
| GET | /workspaces/{id}/repos | Repositories available on the connection |
| GET | /sessions/{id}/changes | Staged changes — nothing written yet |
| POST | /sessions/{id}/changes/apply | Write staged changes to the real workspace |
| POST | /sessions/{id}/changes/discard | Discard 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"
}
targetis"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
| Method | Path | Purpose |
|---|---|---|
| POST | /sessions | Start a council |
| GET | /sessions | List |
| GET | /sessions/{id} | Detail |
| POST | /sessions/{id}/cards | Add a model's card |
| POST | /cards/{id}/regenerate | Regenerate a card |
| DELETE | /cards/{id} | Remove a card |
| GET | /cards/{id}/diff | Diff against another card |
| GET | /cards/{id}/versions | Version history |
| POST | /cards/{id}/restore | Restore a version |
| GET | /cards/{id}/export | Export |
| POST | /cards/{id}/messages | Message a single card |
| GET | /cards/{id}/messages | Card conversation |
| POST | /sessions/{id}/chat | Message the whole council |
| POST | /sessions/{id}/merge | Merge cards into a final |
| POST | /sessions/{id}/debate | Run a debate round |
| GET | /sessions/{id}/events | Replay events |
| WS | /api/council/ws/{id} | Live event stream |
Studios
Images — /api/images
| Method | Path | Purpose |
|---|---|---|
| POST | `` | Generate |
| GET | `` | List jobs |
| DELETE | /{job_id} | Delete |
| POST | /{job_id}/star | Star |
Video — /api/videos
| Method | Path | Purpose |
|---|---|---|
| POST | `` | Generate |
| GET | `` | List jobs |
| DELETE | /{job_id} | Delete |
| POST | /{job_id}/star | Star |
| POST | /{job_id}/extend | Extend a finished clip |
3D — /api/assets-3d
| Method | Path | Purpose |
|---|---|---|
| POST | /generate | Generate |
| GET | /assets | List |
| GET | /assets/{id} | Detail |
| GET | /assets/{id}/status | Job 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
| Method | Path | Purpose |
|---|---|---|
| GET | /me | Your team |
| POST | /me | Create |
| POST | /me/invites | Invite by email |
| POST | /invites/{token}/accept | Accept an invitation |
| POST | /me/leave | Leave |
| POST | /me/transfer-ownership | Transfer 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
| Method | Path | Purpose |
|---|---|---|
| GET/POST/PATCH/DELETE | /api/spaces | Spaces |
| POST | /api/uploads | Upload a file |
| GET | /api/files/{message_id} | Files attached to a message |
| POST | /api/transcribe | Speech to text |
| GET | /api/flags | Feature flags (authenticated) |
| GET | /api/cms | Marketing content |
| GET/PATCH/POST | /api/agents | Per-model preferences |
| GET | /api/connectors/providers | Available connector providers |
| GET | /api/connectors/{provider}/authorize | Begin OAuth |
| GET | /api/connectors/{provider}/callback | OAuth 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:
| Code | Meaning |
|---|---|
| 4403 | The 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.
limitcan only make a page smaller, never larger. - The response header
X-Next-Seqcarries the sequence to pass asafter_seqon 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_seqwith theX-Next-Seqheader. - 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.