Authentication
Every MIOSA API request carries a single Authorization: Bearer <token> header.
The token is either an API key (prefix msk_) or a JWT from POST /auth/login.
One key covers the entire platform - computers, desktop, files, exec, CUA agent, OSA, credits, and (with the right role) admin. No separate keys for AI, no per-product configuration.
Credential hierarchy
API Keys
Key format
msk_<role>_<random> | Prefix | Role | Capabilities |
|---|---|---|
msk_u_... | user | Compute, desktop, files, CUA, OSA, credits |
msk_a_... | admin | Everything above + /api/v1/admin/* |
msk_p_... | platform | Tenant-wide automation issued by the MIOSA platform |
Role is orthogonal to purpose, which picks the backend the key can call:
purpose: api- compute, desktop, files, CUA, OSA, creditspurpose: optimal- AI/LLM proxy (/v1/chat/completions,/v1/responses)
If you need both, create two keys. They share the msk_ family so SDK
configuration is identical.
Creating an API key
curl -X POST https://api.miosa.ai/api/v1/api-keys
-H "Authorization: Bearer YOUR_JWT_TOKEN"
-H "Content-Type: application/json"
-d '{
"name": "Production SDK Key",
"key_type": "user",
"purpose": "api"
}' {
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"key": "msk_u_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
"key_prefix": "msk_u_a1b2c3",
"name": "Production SDK Key",
"key_type": "user",
"key_purpose": "api",
"rate_limit_rpm": 60,
"status": "active",
"created_at": "2026-04-17T00:00:00Z"
}
} User-role callers can only mint user keys. Admin and platform callers may
mint any key_type. To issue admin keys programmatically, use the admin
variant at POST /admin/api-keys (operator-internal - not part of the public docs).
Using an API key
Pass the key in the Authorization header:
curl https://api.miosa.ai/api/v1/computers
-H "Authorization: Bearer msk_u_a1b2c3d4e5f6..." All five SDKs read MIOSA_API_KEY from the environment by default:
Managing keys
| Action | Endpoint |
|---|---|
| List your keys | GET /api/v1/api-keys |
| Create a key | POST /api/v1/api-keys |
| Revoke a key | DELETE /api/v1/api-keys/{id} |
Revoking a key flips its status to revoked. The change is cached and
enforced within seconds - subsequent requests return 401 Unauthorized.
Revocation is irreversible; create a new key if you need access restored.
Admin keys
msk_a_* and msk_p_* keys unlock everything under /api/v1/admin/* -
user management, tenant operations, credit grants, fleet-wide computer
control, hosted model routing. These are gated behind the same RequireAdmin plug as admin JWTs.
User-role callers hitting an admin endpoint receive 403 Forbidden.
Admin endpoints are operator-internal and not documented publicly.
Preview tokens
Preview tokens (mp_<base64url>) are short-lived signed tokens for embedding a sandbox preview in an <iframe> without exposing an msk_* key to the browser.
Your backend mints a preview token server-side and passes it to the <MiosaPreview> UI component. The component includes it automatically - no Authorization header needed in browser code.
Minting a preview token
curl -X POST https://api.miosa.ai/api/v1/sandboxes/sbx_abc123/preview-token
-H "Authorization: Bearer msk_u_..."
-H "Content-Type: application/json"
-d '{"expires_in": 3600}' {
"data": {
"token": "mp_eyJhbGciOiJFZERTQSIs...",
"expires_at": "2026-05-25T13:00:00Z",
"sandbox_id": "sbx_abc123"
}
} In the browser, pass the token to the <MiosaPreview> component:
<MiosaPreview token="mp_eyJhbGciOiJFZERTQSIs..." sandboxId="sbx_abc123" />
See <MiosaPreview> and other UI components and the Preview Tokens API reference for the full component API and token validation spec.
- Format:
mp_<base64url>- signed, not opaque; do not attempt to decode. - TTL: Default 1 hour; configurable via
expires_in(max 24 hours). - Scope: Read-only access to the specific sandbox’s preview port. Cannot create or mutate resources.
- Revocation: Preview tokens cannot be individually revoked. Destroying the sandbox invalidates all outstanding tokens for it.
Share tokens
Share tokens (ms_<base64url>) turn a sandbox preview into a public read-only URL that anyone with the link can visit - no MIOSA account required.
Minting a share URL
curl -X POST https://api.miosa.ai/api/v1/sandboxes/sbx_abc123/shares
-H "Authorization: Bearer msk_u_..."
-H "Content-Type: application/json"
-d '{"expires_in": 86400}' {
"data": {
"token": "ms_eyJhbGciOiJFZERTQSIs...",
"url": "https://5173-sbx-abc123.sandbox.miosa.app?token=ms_eyJhbGciOiJFZERTQSIs...",
"expires_at": "2026-05-26T00:00:00Z",
"sandbox_id": "sbx_abc123"
}
} - Format:
ms_<base64url>- the token IS the URL query parameter (?ms=ms_...). - Access: Public, read-only, unauthenticated - anyone with the URL can view the preview.
- TTL: Set via
expires_inin seconds. No maximum enforced; omit for a non-expiring share. - Revocation:
DELETE /api/v1/sandboxes/:id/shares/:tokenrevokes immediately.
See the Sandbox Shares API reference and Integrations: Preview Tokens.
Token comparison
msk_* API key | JWT | Browser token | mp_ Preview token | ms_ Share token | |
|---|---|---|---|---|---|
| Scope | Full workspace | User session | Single sandbox | Single sandbox preview | Single sandbox preview |
| TTL | Until revoked | ~1 h (refresh 7 d) | Configurable (default 1 h) | Configurable (default 1 h) | Configurable |
| How to mint | Dashboard or POST /api/v1/api-keys | POST /api/v1/auth/login | POST /api/v1/browser-tokens | POST /api/v1/sandboxes/:id/preview-token | POST /api/v1/sandboxes/:id/shares |
| Who uses it | Server | Server / browser (UI) | Browser | Browser / iframe | Anyone (unauthenticated) |
| Can revoke | Yes | No (short TTL) | No (short TTL) | No | Yes |
| Creates resources | Yes | Yes | No | No | No |
White-label auth note
The tenant.preview_domain.* and tenant.branding.* configuration endpoints accept both msk_* API keys and JWTs. These are the only tenant-level config routes accessible with an API key - all other tenant administration routes require a JWT from an admin session.
# Setting a preview domain with an API key (allowed)
curl -X PUT https://api.miosa.ai/api/v1/tenant/preview-domain
-H "Authorization: Bearer msk_a_..."
-H "Content-Type: application/json"
-d '{"domain": "preview.yourdomain.com"}'
# Fetching tenant branding with an API key (allowed)
curl https://api.miosa.ai/api/v1/tenant/branding
-H "Authorization: Bearer msk_a_..." See the Tenant Preview Domain and Tenant Branding API references for the full configuration spec.
JWT Tokens
JWT tokens are used by the MIOSA web UI and SSE ticket flows. They are issued on login and carry user identity + tenant context.
Obtaining a token
curl -X POST https://api.miosa.ai/api/v1/auth/login
-H "Content-Type: application/json"
-d '{"email": "user@example.com", "password": "your_password"}' {
"token": "eyJhbGciOiJIUzUxMiIs...",
"refresh_token": "eyJhbGciOiJIUzUxMiIs...",
"user": { "id": "...", "email": "user@example.com" }
} Token lifecycle
| Token | Lifetime | Refresh |
|---|---|---|
| Access token | ~1 hour | Use refresh token |
| Refresh token | 7 days | Re-login required |
curl -X POST https://api.miosa.ai/api/v1/auth/refresh
-H "Content-Type: application/json"
-d '{"refresh_token": "eyJhbGciOiJIUzUxMiIs..."}' SSE streams
Server-Sent Event endpoints (CUA session events) cannot use a standard Authorization header because EventSource does not support custom headers.
Instead, mint a one-time ticket first:
# 1. Ask for a ticket (Bearer-authed - API key or JWT both work)
curl -X POST
https://api.miosa.ai/api/v1/computers/{id}/cua/sessions/{session_id}/sse-ticket
-H "Authorization: Bearer msk_u_..."
# {"ticket":"sset_abc123","expires_in":3600,"session_id":"..."}
# 2. Open the stream with the ticket as a query param
curl https://api.miosa.ai/api/v1/computers/{id}/cua/sessions/{session_id}/events?ticket=sset_abc123 The ticket is single-use and expires after one hour.
Rate limits
| Scope | Limit | Window |
|---|---|---|
Developer API (/api/v1/*) | 300 requests | Per minute per key |
Admin API (/api/v1/admin/*) | 300 requests | Per minute per key |
Auth endpoints (/auth/login, /auth/register, /auth/refresh) | 60 requests | Per minute per IP |
When rate limited, the API returns 429 Too Many Requests with a Retry-After header.
Rate-limit status is surfaced in every response:
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 287
X-RateLimit-Reset: 1712700060 Error codes
| Status | Code | Meaning |
|---|---|---|
| 401 | UNAUTHORIZED | Missing / invalid / revoked credential |
| 403 | FORBIDDEN | Valid credential but insufficient role |
| 429 | RATE_LIMITED | Too many requests |
Security best practices
- Never commit API keys to version control.
- Use environment variables or a secrets manager.
- Rotate keys on exposure - revoke and create a new one.
- Scope by role - hand out
msk_u_*keys to most callers; reservemsk_a_*/msk_p_*for operational tooling. - Separate keys for dev and prod - easier to revoke blast radius.
- Restrict by IP (admin keys) - pass
allowed_ipswhen minting viaPOST /admin/api-keysto reject traffic from unexpected sources.