---
title: SCWP wire reference
order: '20'
summary: "Every SCWP verb by type code \u2014 frame shape, request body, response\
  \ body, and the errors that can come back."
path: reference/scwp
status: published
---

SCWP — ScaiClip Wire Protocol — is a binary protocol over WebSocket. All client-originated messages are dispatched by an 8-bit **type code**. Bodies are canonical CBOR (RFC 8949 §4.2.1).

**Endpoint.** `wss://scaigrid.scailabs.ai/v1/modules/scaiclip/ws`. Sub-protocol `scaiclip/1`. Auth via `?token=<sgk_>` query param or `Authorization: Bearer <jwt>` header.

**Frame format.** 16-byte header (little-endian) + CBOR body. See §2 of the [wire spec](https://github.com/scailabs/scaigrid/tree/main/docs/integrations/scaiclip/scwp-wire-protocol-v0.1.1.md) for the layout. Every request carries a monotone `request_id`; the response echoes it. Grid-originated pushes use the high half (`0x8000_0000+`).

## Client → grid verbs

### Channel verbs

| Type | Verb | Body | Response |
|---|---|---|---|
| `0x01` | `channel.list` | `{}` | `{1: [ChannelInfo], ...}` |
| `0x02` | `channel.subscribe` | `{1: [channel_id]}` | `{1: [channel_id]}` |
| `0x03` | `channel.unsubscribe` | `{1: [channel_id]}` | `{1: [channel_id]}` |
| `0x04` | `channel.sync` | `{1: channel_id, 2: from_seq, 3: limit}` | `{1: [Envelope], 2: {seq: ciphertext}, 3: head_seq, 4: [tombstoned_seq]}` |
| `0x05` | `channel.create` | `{1: kind, 2: name, 3: group, 4: peer, 5: policy_overrides, 6: initial_wraps}` | `{1: channel_id, 2: 1}` |
| `0x06` | `channel.devices` | `{1: channel_id}` | `{1: [{1: device_id, 2: subject, 3: kem_pub, 4: sig_pub, 5: online}]}` |

`ChannelInfo` is `{1: channel_id_bytes, 2: kind_enum, 4: perms_bitfield, 5: key_version, 6: {1: ttl_default_s, 2: ttl_max_s, 3: history_depth}}`.

### Clip verbs

| Type | Verb | Body | Response |
|---|---|---|---|
| `0x10` | `clip.publish` | `{1: Envelope, 2: ciphertext, 3: [WrapRecord]}` (wraps required iff `ONE_SHOT`) | `{1: seq, 2: grid_ts, 3: ttl_effective_s, 4: replayed}` |
| `0x11` | `clip.fetch` | `{1: channel_id, 2: seq}` or `{3: clip_id}` | `{1: Envelope, 2: ciphertext}` |
| `0x12` | `clip.pin` | `{1: channel_id, 2: seq}` | `{}` |
| `0x13` | `clip.unpin` | `{1: channel_id, 2: seq}` | `{}` |
| `0x14` | `clip.delete` | `{1: channel_id, 2: seq}` | `{}` |
| `0x15` | `clip.claim` | `{1: channel_id, 2: seq}` | `{1: WrapRecord, 2: ciphertext}` |

### Key wrap verbs

| Type | Verb | Body | Response |
|---|---|---|---|
| `0x20` | `key.wraps.get` | `{1: channel_id, 2: key_version}` | `{1: WrapRecord}` or `E_NOT_FOUND` |
| `0x21` | `key.wraps.put` | `{1: channel_id, 2: key_version, 3: [WrapRecord]}` | `{}` |
| `0x22` | `key.rekey.complete` | `{1: channel_id, 2: new_key_version}` | `{}` |

`WrapRecord = {1: device_id, 2: hpke_enc (32 bytes), 3: wrap_blob, 4: key_version}`.

### Presence

| Type | Verb | Body | Response |
|---|---|---|---|
| `0x30` | `presence.devices` | `{}` | `{1: [{1: device_id, 2: online, 3: last_seen}]}` |

### Scratch storage

| Type | Verb | Body | Response |
|---|---|---|---|
| `0x40` | `scratch.reserve` | `{1: size, 2: sha256_hex, 3: mime_hint, 4: sharing_mode, 5: intended_ttl_s}` | `{1: object_id, 2: upload_url, 4: expires_at_ms, 5: quota_warning?}` |
| `0x41` | `scratch.commit` | `{1: object_id, 2: sha256_hex, 3: [WrapRecord]}` | `{1: object_id, 2: committed_at_ms, 3: expires_at_ms}` |
| `0x42` | `scratch.get` | `{1: object_id}` | `{1: object_id, 2: download_url, 3: size, 4: sha256_hex}` |
| `0x43` | `scratch.list` | `{1: cursor?, 2: limit?}` | `{1: [{1: object_id, 2: size, 3: mime, 4: sharing_mode, 5: created_at_ms, 6: expires_at_ms}]}` |
| `0x44` | `scratch.delete` | `{1: object_id}` | `{1: object_id, 2: deleted_at_ms}` |
| `0x45` | `scratch.publish` | `{1: object_id, 2: expires_at_ms, 3: download_limit, 4: password_bound}` | `{1: token, 2: public_url, 3: revoke_token, 4: expires_at_ms}` |

### Media proxy (`ref_origin`)

| Type | Verb | Body | Response |
|---|---|---|---|
| `0x50` | `media.fetch` | `{1: channel_id, 2: seq}` | `{1: MediaRef, 2: transfer_id}` or `E_ORIGIN_OFFLINE` |
| `0x52` | `media.pull_stream` | `{1: transfer_id, 2: chunk_index, 3: chunk_bytes, 4: is_final}` (origin → grid) | `{1: "ok", 2: chunk_index, 3: is_final}` |
| `0x53` | `media.pull_range` | `{1: transfer_id | sha256_hex, 2: chunk_index}` | `{1: chunk_index, 2: chunk_bytes?, 3: is_final}` |
| `0x54` | `media.cancel` | `{1: transfer_id}` | `{1: "cancelled"}` |

### SCSG (user-side)

| Type | Verb | Body | Response |
|---|---|---|---|
| `0x62` | `session.deny` | `{1: grant_id, 2: reason}` | `{1: "denied"}` |

## Grid → client push events

| Type | Event | Body |
|---|---|---|
| `0x80` | `ok` | (response body per verb) |
| `0x81` | `error` | `{1: code, 2: wire_name, 3: message, 4: detail}` — see [error codes](./errors.md) |
| `0x90` | `clip.new` | `{1: Envelope, 2: ciphertext?}` (inline for small clips) |
| `0x91` | `clip.expired` | `{1: channel_id, 2: seq, 3: reason}` |
| `0x92` | `clip.claimed` | `{1: channel_id, 2: seq, 3: claimant_subject, 4: claimant_device_id, 5: at_ms}` |
| `0x93` | `clip.deleted` | `{1: channel_id, 2: seq}` |
| `0x94` | `rekey.required` | `{1: channel_id, 2: new_key_version, 3: reason}` |
| `0x95` | `channel.membership` | `{1: channel_id, 2: added_devices?, 3: removed_devices?}` |
| `0x96` | `wrap.request` | `{1: channel_id, 2: requester_device_id, 3: requester_subject, 4: requester_kem_pub, 5: key_version}` |
| `0x97` | `wraps.ready` | `{1: channel_id, 2: target_device_id, 3: key_version}` |
| `0xA0` | `session.grant_request` | `{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: service_display_name}` |
| `0xB0` | `presence.device_state` | `{1: device_id, 2: online, 3: at_ms}` |

## Envelope key registry

| Key | Meaning | Notes |
|---|---|---|
| 1 | `clip_id` | UUIDv7 (client-generated) |
| 2 | `channel_id` | bytes(16) |
| 3 | `seq` | grid-assigned |
| 4 | `sender` | text (`usr_alice`) |
| 5 | `device_id` | bytes(16) or text (nanoid) |
| 6 | `client_ts` | ms |
| 7 | `grid_ts` | ms, grid-assigned |
| 8 | `type_hint` | 0-4 |
| 9 | `size_class` | 0 inline · 1 reference |
| 10 | `cflags` | bit0 SENSITIVE · bit1 ONE_SHOT · bit2 PINNED |
| 11 | `ttl_s` | client-requested, grid-clamps |
| 12 | `key_version` | channel key at publish time |
| 13 | `source_app` | optional identifier ("mobile", "browser", …) |
| 14 | `payload_hash` | SHA-256 of ciphertext |
| 15 | `sig` | Ed25519 over canonical CBOR of keys 1-14 |
| 20 | `payload_ref` | `MediaRef` map (optional) |

## Related

- [SCSG verb reference](./scsg.md) — the service-side sub-protocol.
- [Error codes](./errors.md) — every `E_*` value and its detail shape.
- [Channels, envelopes, wraps, claims](../concepts/channels.md) — the conceptual overview.
- [Encryption & consent](../concepts/encryption.md) — the crypto profile.