API reference
The REST surface is mounted at /v1/modules/scailink/ and authenticates with the standard ScaiGrid bearer token. The desktop WebSocket lives at /v1/scailink/ws and authenticates with a user JWT in the Authorization header. Responses use ScaiGrid's standard envelope: { "data": ... } for success, { "error": ... } for failures.
Auth discovery#
GET /v1/scailink/auth/discover#
Public, unauthenticated. The first endpoint a desktop client calls. Returns OAuth metadata so the client knows where to send the user for sign-in.
1 | |
Response fields: issuer, authorization_endpoint, token_endpoint, device_authorization_endpoint, userinfo_endpoint, jwks_uri, scopes_supported, grant_types_supported, client_id, audience, gateway_ws.
Desktop WebSocket#
WS /v1/scailink/ws#
Authenticated WebSocket carrying JSON-RPC 2.0 frames. The first frame from the client must be scailink/session_init; anything else closes the connection with code 4002.
Client-to-server methods:
| Method | Purpose |
|---|---|
scailink/session_init |
Handshake with device info, platform, capability catalog, audit settings. |
scailink/heartbeat |
Keepalive; sent every heartbeat_interval_ms (default 30000). |
scailink/catalog_update |
Push add/remove deltas to the live catalog without reconnecting. |
scailink/consent_response |
Answer an outstanding consent prompt (approved or denied). |
scailink/session_terminate |
Clean shutdown signal. |
Server-to-client methods:
| Method | Purpose |
|---|---|
scailink/session_init_ack |
Response to the handshake; includes session_id, heartbeat_interval_ms, grace_period_ms. |
scailink/tool_invoke |
Ask the client to run a tool; reply with the matching id. |
scailink/resource_read |
Ask the client to read a resource. |
scailink/prompt_get |
Ask the client to render a prompt. |
scailink/consent_request |
Ask the user to approve or deny a pending invocation. |
scailink/policy_update |
Push an updated consent policy mid-session. |
Custom JSON-RPC error codes: -32000 TOOL_ERROR, -32001 CONSENT_DENIED, -32002 CONSENT_TIMEOUT, -32003 SCOPE_BLOCKED, -32004 QUEUE_FULL, -32005 TIMEOUT, -32006 CLIENT_DISCONNECTED.
Sessions#
GET /sessions#
List the caller's own active ScaiLink sessions across all their devices.
GET /sessions/all#
List all active sessions in the caller's tenant. Requires scailink:manage.
GET /users/{user_id}/sessions#
List active sessions for a specific user. Cross-user reads require same-tenant access; super admin bypasses tenant isolation. user_id accepts either a ScaiGrid UUID or a ScaiKey usr_... id.
Capabilities#
GET /capabilities#
Aggregated capability catalog for the caller — tools, resources, and prompts across all of the caller's connected devices.
GET /users/{user_id}/capabilities#
Same shape, for a specific user. Tenant-bounded unless the caller is super admin.
GET /device/{device_id}#
Capability catalog for a single device. Useful when a user has multiple machines connected and you want to invoke against one specifically.
Invocations#
All four below require scailink:invoke.
POST /users/{user_id}/tools/{tool_name}/invoke#
Invoke a tool on the user's connected device.
| Body field | Notes |
|---|---|
arguments |
Tool-specific input. |
conversation_id |
Optional; threaded through to the audit log and consent reuse. |
timeout |
Seconds. Default 30. |
1 2 3 4 | |
Returns { "data": { "result": ..., "error": null, "duration_ms": 145, "device_id": "..." } }.
POST /users/{user_id}/resources/{resource_uri:path}/read#
Read a resource (e.g. a file URI) from the user's device. The URI is in the path; encode / characters appropriately.
POST /users/{user_id}/prompts/{prompt_name}/get#
Retrieve a prompt from the user's device.
| Body field | Notes |
|---|---|
arguments |
Prompt arguments per the MCP prompts/get shape. |
timeout |
Seconds. Default 15. |
POST /consent/{consent_id}/resolve#
Resolve a pending consent request out of band — when the user approves or denies through a UI separate from the WebSocket itself.
| Body field | Notes |
|---|---|
approved |
Boolean. |
comment |
Free-form audit annotation. |
Audit#
GET /audit#
Tenant-wide audit log. Requires scailink:audit.
Query parameters: limit (1-200, default 50), offset (default 0).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
GET /users/{user_id}/audit#
Per-user audit log. Same shape, scoped to one user. scailink:audit required.
Cloud MCP registry#
All endpoints below are under /v1/modules/scailink/remote-servers. Every call requires scailink:remote.use as the precondition; mutating calls additionally require manage_own (for personal servers) or manage_tenant (for tenant-shared).
GET /remote-servers#
List servers visible to the caller — their own personal servers plus any tenant-shared servers in the caller's tenant.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | |
GET /remote-servers/{id}#
Detail view. Same fields as list plus capabilities (discovered tools/resources/prompts), credential_fields (the field names you can rotate), and credential_oldest_days.
POST /remote-servers#
Register a new server. Returns 201 Created.
| Field | Required | Notes |
|---|---|---|
name |
yes | 1-120 chars; drives the slug. |
endpoint_url |
yes | 1-500 chars. |
auth_type |
yes | none, bearer, or api_key_header. |
credentials |
conditional | Flat {field_name: value}. Required unless auth_type=none. Field names map to outbound HTTP header names verbatim. |
description |
no | Up to 1000 chars. |
is_tenant_shared |
no | Default false. True needs scailink:remote.manage_tenant. |
forward_user_id |
no | Default false. True adds X-ScaiGrid-User. |
consent_tier |
no | auto (default), confirm, or locked. |
transport |
no | streamable_http (default) or sse. |
Registration triggers an immediate discovery. The row is committed even if discovery fails (status='error').
DELETE /remote-servers/{id}#
Remove the server. Cascades through mod_scailink_remote_credential and mod_scailink_remote_capability. Personal servers need manage_own; tenant-shared need manage_tenant.
POST /remote-servers/{id}/refresh#
Force re-discovery. Updates capability rows and health. Gated on scailink:remote.use (same perm as listing).
PUT /remote-servers/{id}/credentials/{field}#
Rotate one credential field. Idempotent — overwrites the existing ciphertext and DEK.
Body: { "value": "..." }.
Personal servers need manage_own; tenant-shared need manage_tenant.
Errors#
All endpoints return ScaiGrid's standard error envelope:
1 2 3 4 5 6 7 8 | |
ScaiLink-specific codes:
| Code | Status | Meaning |
|---|---|---|
SCAILINK_REMOTE_SERVER_NOT_FOUND |
404 | Server id not visible to the caller. |
SCAILINK_REMOTE_LIMIT_EXCEEDED |
429 | Tenant hit the registration cap (100). |
SCAILINK_REMOTE_INVALID_CONFIG |
400 | Registration body didn't validate (bad auth_type, transport, consent_tier). |
SCAILINK_REGISTRY_DISABLED |
503 | Platform KEK is unset; cloud registry feature is off in this deployment. |
WebSocket close codes you may see from the desktop bridge:
| Code | Meaning |
|---|---|
| 4001 | Authentication failed (bad or missing JWT). |
| 4002 | First frame was not session_init, or session_init params didn't validate. |
| 4000 | Generic close from the gateway side. |