REST API
Base URL: https://<your-site>/api (through the website) or the API service directly. The machine-readable
OpenAPI 3 document is served at /docs/json on the API service, with an interactive explorer at /docs.
Export it with npm run openapi -w @skein/api.
Authentication
| Client | How |
|---|---|
| Browser | skein_session cookie + x-csrf-token header (value of the skein_csrf cookie) on non-GET requests |
| Unity Editor | Authorization: Bearer <access token> from the device flow; refresh with POST /v1/auth/token/refresh |
| Tools / CI | Authorization: Bearer sk_… project API key |
Errors
Errors are JSON: { "error": { "code": "PLAN_LIMIT", "message": "…human readable…", "details": { … } } }.
message is written to be shown to users as-is. Validation errors (VALIDATION_FAILED) include details.fields.
Missing and inaccessible resources both return 404.
| Status | Code examples |
|---|---|
| 400 | VALIDATION_FAILED, BAD_REQUEST, device-flow codes authorization_pending, slow_down, access_denied, expired_token |
| 401 | UNAUTHENTICATED, INVALID_CREDENTIALS |
| 402 | PLAN_LIMIT (with details.limit) |
| 403 | FORBIDDEN, CSRF_FAILED |
| 404 | NOT_FOUND |
| 409 | EMAIL_TAKEN, ALREADY_MEMBER, LAST_OWNER, SESSION_ENDED |
| 429 | RATE_LIMITED |
Endpoints
Auth
POST /v1/auth/signup{email, password, displayName}·POST /v1/auth/login·POST /v1/auth/logout·GET /v1/auth/csrfPOST /v1/auth/verify-email{token}·POST /v1/auth/verify-email/resendPOST /v1/auth/password/forgot{email}·POST /v1/auth/password/reset{token, password}- Device flow:
POST /v1/auth/device/code→GET /v1/auth/device/{userCode}→POST /v1/auth/device/approve→POST /v1/auth/device/token POST /v1/auth/token/refresh{refreshToken}·POST /v1/auth/token/revoke- OAuth:
GET /v1/auth/oauth/providers,GET /v1/auth/oauth/{provider}/start?redirect=/path,GET /v1/auth/oauth/{provider}/callback
Account
GET /v1/me·PATCH /v1/me·POST /v1/me/password·GET /v1/me/sessions·DELETE /v1/me/sessions/{id}
Organizations
GET /v1/orgs·POST /v1/orgs·PATCH /v1/orgs/{id}GET|POST /v1/orgs/{id}/members·DELETE /v1/orgs/{id}/members/{userId}GET /v1/orgs/{id}/audit-logs(Studio)
Projects & members
GET /v1/projects(with live online counts and active sessions) ·POST /v1/projects·GET|PATCH|DELETE /v1/projects/{id}GET /v1/projects/{id}/activityGET /v1/projects/{id}/members·PATCH|DELETE /v1/projects/{id}/members/{userId}POST /v1/projects/{id}/invitations·DELETE /v1/projects/{id}/invitations/{invitationId}GET /v1/invitations/{token}·POST /v1/invitations/accept
Sessions
GET|POST /v1/projects/{id}/sessions·GET /v1/sessions/{id}·POST /v1/sessions/{id}/endPOST /v1/sessions/{id}/ticket→{ticket, url, expiresIn}— see protocol.mdGET /v1/sessions/{id}/participants— participation history
History & snapshots
GET /v1/projects/{id}/changes?sessionId&objectId&userId&before&limit(cursor = change id)GET|POST /v1/projects/{id}/snapshots— create withContent-Type: application/x-skein-scene+gzip, metadata in the query (name, scenePath, sceneGuid, sessionId?, description?, unityVersion?)GET|DELETE /v1/snapshots/{id}·GET /v1/snapshots/{id}/content·POST /v1/snapshots/{id}/restore{sessionId}
API keys
GET|POST /v1/projects/{id}/api-keys·DELETE /v1/projects/{id}/api-keys/{keyId}
Billing
GET /v1/billing/plans(public) ·GET /v1/orgs/{id}/billingPOST /v1/orgs/{id}/billing/checkout{plan, interval}→{url}·POST /v1/orgs/{id}/billing/portal→{url}POST /v1/billing/webhook(Stripe, signature-verified)
Operations
GET /healthz·GET /readyz·GET /metrics(bearerMETRICS_TOKEN, or loopback only)
Example
curl -s https://app.skein.dev/api/v1/projects/$PROJECT/changes?limit=20 \
-H "Authorization: Bearer $SKEIN_API_KEY" | jq '.changes[] | "\(.user.name) \(.kind) \(.objectName)"'