SCSG verb reference
SCSG is the sub-protocol server-side services (ScaiWave, ScaiTerm, ScaiDrive-server, ScaiEdit-server) use to obtain bounded, user-consented clipboard access. It runs on its own WebSocket endpoint separate from the user-facing scaiclip/1.
For the conceptual model, see Server-side integrations.
Endpoint#
1 | |
Sub-protocol string: scsg/1.
Auth: OAuth2 client_credentials JWT via ?token=<jwt> query param or Authorization: Bearer <jwt> header. The token's sub claim (== client_id) must correspond to a registered service_id in the SCSG registry.
Naming convention (M1): client_id starts with cli_<service_id>_… (e.g. cli_scaiwave_prod_a1b2c3). Future ScaiKey versions will carry service_id as an explicit claim.
Verbs#
Client (service) → grid#
| Type | Verb | Body | Response |
|---|---|---|---|
0x60 |
session.open |
`{1: session_id, 2: user_id, 3: ephemeral_kem_pub, 4: ephemeral_sig_pub, 5: {1: channel_ids | 'user_default', 2: capabilities}, 6: duration_max_s, 7: reason}` |
0x61 |
session.close |
{1: grant_id, 2: reason} |
{1: 'closed', 2: closed_at_ms} |
0x63 |
session.list |
{} |
{1: [{1: grant_id, 2: service_id, 3: user_id, 4: status, 5: capabilities, 6: expires_at_ms}]} |
Grid → service push#
| Type | Event | Body |
|---|---|---|
0xA1 |
session.ready |
{1: grant_id, 2: [channel_id_bytes], 3: expires_at_ms, 4: consenting_device_id} |
0xA2 |
session.denied |
{1: grant_id, 2: reason} |
0xA3 |
session.expired |
{1: grant_id} |
0xA4 |
session.revoked |
{1: grant_id, 2: reason} |
User-side (scaiclip/1 endpoint)#
| Type | Verb | Body | Response |
|---|---|---|---|
0x62 |
session.deny |
{1: grant_id, 2: reason} |
{1: 'denied'} |
0xA0 |
session.grant_request (grid → user) |
{1: grant_id, 2: service_id, 3: reason, 4: {1: channel_ids, 2: capabilities}, 5: duration_max_s, 6: ephemeral_sig_pub, 7: consent_deadline_ms, 8: display_name} |
— |
User consents by installing wraps for the ephemeral device via the standard key.wraps.put verb targeting dev_scsg_<service_id>_<session_id>. The grid's implicit-consent hook detects this pattern and transitions the grant to active.
Capabilities bitfield#
scope.capabilities is a u32 with these flags:
| Bit | Name | Description |
|---|---|---|
0x01 |
READ | Subscribe + fetch (clip.fetch, channel.sync) |
0x02 |
WRITE | Publish (clip.publish) |
0x04 |
CLAIM | Consume one-shot clips (subject to tenant policy) |
0x08 |
SCRATCH_READ | Read scratch objects |
0x10 |
SCRATCH_WRITE | Upload scratch objects |
0x20 |
PUBLIC_SHARE | Mint public share links |
Services SHOULD request the minimum bit set for their function. Consent UIs enumerate capabilities in human terms.
Service registry#
Only pre-registered service_id values may open sessions.
service_id |
Display | Default capabilities |
|---|---|---|
scaiwave |
ScaiWave (voice) | READ · WRITE |
scaiterm |
ScaiTerm (terminal) | READ · WRITE · CLAIM |
scaidrive |
ScaiDrive (files) | READ · SCRATCH_READ · SCRATCH_WRITE |
scaiedit |
ScaiEdit (editor) | READ · WRITE · SCRATCH_READ |
Unlisted service_id → E_UNKNOWN_SERVICE.
Third-party service integration requires a manual registration step (email platform@scailabs.ai) plus a ScaiKey OAuth app.
Tenant policy#
Each service must be opted in by the tenant admin:
Column in mod_scaiclip_tenant_policy |
Default |
|---|---|
allow_scsg_scaiwave |
false |
allow_scsg_scaiterm |
false |
allow_scsg_scaidrive |
false |
allow_scsg_scaiedit |
false |
allow_agent_claim |
false — gates the CLAIM capability regardless of service |
max_session_duration_s |
14400 (4 hours) — clamps duration_max_s |
Missing tenant policy row → all flags default false. Attempting session.open for a service the tenant hasn't opted into → E_FORBIDDEN.
Rate limits#
| Limit | Setting | Default |
|---|---|---|
session.open per (tenant, service_id) |
scaiclip_scsg_session_open_per_s / _burst |
5/s, burst 20 |
Concurrent sessions per (tenant, service_id) |
scaiclip_scsg_max_concurrent_per_service |
100 |
| Consent window | scaiclip_scsg_consent_window_s |
60 s |
| Absolute max session duration | scaiclip_scsg_absolute_max_duration_s |
4 h |
| Expiry sweep cadence | scaiclip_scsg_expiry_sweep_s |
30 s |
Audit stream#
Every SCSG lifecycle event lands in audit_log:
1 2 3 4 5 6 7 8 | |
Every row's details field carries service_id, grant_id, user_id, capabilities, duration_max_s, and where applicable consenting_device_id.
Reference client#
The Python reference client at modules/scaiclip/services/scsg_client.py (in the ScaiGrid repo) wraps the WS protocol so consumer services call open_session / wait_ready / close_session async methods without touching CBOR.
Related#
- Server-side integrations — conceptual overview.
- Encryption & consent — how SCSG preserves E2E.
- Integrating a service consumer — end-to-end walkthrough.
- Error codes.