rstack-online/docs/API-REFERENCE.md

793 lines
25 KiB
Markdown

# r*.online Ecosystem — API Reference
> Complete endpoint documentation for all modules
**Version**: 1.0.0
**Last Updated**: February 2026
---
## Authentication
All modules authenticate via **EncryptID** (WebAuthn passkeys + JWT).
### Token Acquisition
```
POST https://encryptid.jeffemmett.com/api/auth/start
→ { challenge, allowCredentials }
POST https://encryptid.jeffemmett.com/api/auth/complete
← { token: "eyJ..." }
```
### Token Transport
Attach the JWT to requests using one of:
| Method | Format | Use Case |
|--------|--------|----------|
| Header | `Authorization: Bearer <token>` | API calls |
| Cookie | `encryptid_token=<token>` | Browser navigation |
| Query | `?token=<token>` | WebSocket upgrade |
| Subprotocol | `Sec-WebSocket-Protocol: encryptid.<token>` | WebSocket upgrade |
### JWT Claims
```json
{
"sub": "user_abc123",
"did": "did:key:z6Mk...",
"username": "ana",
"authLevel": 2,
"aud": ["rspace.online", "rvote.online", "rnotes.online"],
"iat": 1708000000,
"exp": 1708086400
}
```
### Auth Levels
| Level | Name | Grants |
|-------|------|--------|
| 1 | BASIC | Read-only operations |
| 2 | STANDARD | Create and edit own resources |
| 3 | ELEVATED | Moderate other users' resources |
| 4 | CRITICAL | Admin operations, key management |
---
## Space Roles & Permissions
Every request within a space is authorized against a **SpaceRole**:
| Role | Level | Default For |
|------|-------|-------------|
| VIEWER | 0 | Anonymous users in PUBLIC_READ spaces |
| PARTICIPANT | 1 | Anonymous users in PUBLIC spaces, authenticated users in PUBLIC_READ |
| MODERATOR | 2 | Explicitly granted |
| ADMIN | 3 | Space owner, explicitly granted |
### Checking Capabilities
Each module defines a capability map. Example (rVote):
```typescript
import { hasCapability, SpaceRole, RVOTE_PERMISSIONS } from '@encryptid/sdk';
// Returns true if user's role meets minimum for capability
hasCapability(SpaceRole.PARTICIPANT, 'cast_vote', RVOTE_PERMISSIONS); // true
hasCapability(SpaceRole.VIEWER, 'cast_vote', RVOTE_PERMISSIONS); // false
```
---
## Common Patterns
### Error Responses
All modules return errors in this format:
```json
{
"error": "Not authorized",
"code": "FORBIDDEN",
"status": 403
}
```
| Status | Meaning |
|--------|---------|
| 400 | Invalid request body or parameters |
| 401 | Missing or invalid authentication token |
| 403 | Authenticated but insufficient permissions |
| 404 | Resource not found |
| 409 | Conflict (duplicate slug, concurrent edit) |
| 429 | Rate limited |
| 500 | Internal server error |
### Pagination
Modules using Prisma support cursor-based pagination:
```
GET /api/proposals?cursor=abc123&take=20
```
### Space Scoping
Most endpoints are scoped to a space via subdomain:
```
https://crypto.rvote.online/api/proposals → proposals in "crypto" space
https://coop.rnotes.online/api/notebooks → notebooks in "coop" space
```
---
## EncryptID Server
**Base URL**: `https://encryptid.jeffemmett.com`
**Tech**: Hono.js on Bun | PostgreSQL
### Registration
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/api/register/start` | None | Begin WebAuthn registration, returns challenge + options |
| POST | `/api/register/complete` | None | Complete registration, store credential, return JWT |
### Authentication
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/api/auth/start` | None | Begin authentication, returns challenge |
| POST | `/api/auth/complete` | None | Complete authentication, return JWT |
### Session Management
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/session/verify` | Bearer | Verify token validity, returns claims |
| POST | `/api/session/verify` | Body | Verify token passed in request body |
| POST | `/api/session/refresh` | Bearer | Refresh expired token (1h grace period) |
### Credential Management
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/user/credentials` | Bearer | List user's registered passkeys |
### Recovery
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/api/recovery/email/set` | Bearer | Set recovery email address |
| POST | `/api/recovery/email/request` | None | Request recovery email (30-min token) |
| POST | `/api/recovery/email/verify` | None | Verify recovery token, issue new session |
### Membership (NEW — Phase 4)
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/api/spaces/:slug/members` | Admin | Add or update member in space |
| GET | `/api/spaces/:slug/members` | Bearer | List all members of a space |
| GET | `/api/spaces/:slug/members/:did` | Bearer | Get one member's role in space |
| DELETE | `/api/spaces/:slug/members/:did` | Admin | Remove member from space |
---
## rVote — Decision Engine
**Base URL**: `https://{space}.rvote.online`
**Tech**: Next.js App Router | PostgreSQL (Prisma) | NextAuth
### Proposals
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/proposals` | Optional | List proposals (filterable by status) |
| POST | `/api/proposals` | PARTICIPANT | Create new proposal |
| GET | `/api/proposals/:id` | Optional | Get proposal with vote data |
| PATCH | `/api/proposals/:id` | Author | Edit proposal (before voting phase) |
| DELETE | `/api/proposals/:id` | Author | Delete proposal (before voting phase) |
### Voting
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/api/proposals/:id/vote` | PARTICIPANT | Cast or update ranking vote (spend credits) |
| DELETE | `/api/proposals/:id/vote` | PARTICIPANT | Remove vote (refund credits) |
| GET | `/api/proposals/:id/final-vote` | Optional | Get final vote counts (YES/NO/ABSTAIN) |
| POST | `/api/proposals/:id/final-vote` | PARTICIPANT | Cast final vote on promoted proposal |
### Credits
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/user/credits` | Bearer | Get user's available and stored credits |
| POST | `/api/user/credits` | Bearer | Claim accumulated daily credits |
### Spaces
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/spaces` | Bearer | List user's spaces |
| POST | `/api/spaces` | Bearer | Create new space |
| GET | `/api/spaces/:slug` | Optional | Get space details |
| PATCH | `/api/spaces/:slug` | ADMIN | Update space settings |
| DELETE | `/api/spaces/:slug` | Owner | Delete space |
### Members
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/spaces/:slug/members` | Bearer | List space members with roles |
| POST | `/api/spaces/:slug/members` | ADMIN | Add member by email |
| PATCH | `/api/spaces/:slug/members/:userId` | ADMIN | Update member role |
| DELETE | `/api/spaces/:slug/members/:userId` | ADMIN | Remove member |
### Invites
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/spaces/:slug/invites` | ADMIN | List active invite links |
| POST | `/api/spaces/:slug/invites` | ADMIN | Create invite link |
| DELETE | `/api/spaces/:slug/invites/:id` | ADMIN | Revoke invite link |
| GET | `/api/spaces/join/:token` | Bearer | Join space via invite token |
---
## rNotes — Collaborative Notebooks
**Base URL**: `https://{space}.rnotes.online`
**Tech**: Next.js App Router | PostgreSQL (Prisma) | Automerge CRDT
### Notes
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/notes` | Bearer | List notes (filter by notebook, type, tags) |
| POST | `/api/notes` | PARTICIPANT | Create note |
| GET | `/api/notes/:id` | Bearer | Get note with tags |
| PATCH | `/api/notes/:id` | Author/MOD | Update note |
| DELETE | `/api/notes/:id` | Author/MOD | Delete note |
| GET | `/api/notes/search?q=` | Bearer | Full-text search across notes |
### Notebooks
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/notebooks` | Bearer | List notebooks with note counts |
| POST | `/api/notebooks` | PARTICIPANT | Create notebook |
| GET | `/api/notebooks/:id` | Bearer | Get notebook details |
| PATCH | `/api/notebooks/:id` | Owner/MOD | Update notebook metadata |
| DELETE | `/api/notebooks/:id` | Owner/ADMIN | Delete notebook |
| GET | `/api/notebooks/:id/notes` | Bearer | Get notes in notebook |
| GET | `/api/notebooks/:id/canvas` | Bearer | Get canvas shape data for notebook |
### File Uploads
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/api/uploads` | Bearer | Upload file (max 50MB) |
| GET | `/api/uploads/:filename` | Bearer | Download file |
### Voice
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/api/voice/transcribe` | Bearer | Transcribe audio file |
| POST | `/api/voice/diarize` | Bearer | Speaker diarization |
### Canvas Sync
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/api/sync` | Service | Receive shape updates from rSpace canvas |
---
## rSpace — Collaborative Canvas
**Base URL**: `https://{space}.rspace.online`
**Tech**: Hono.js on Bun | Automerge CRDT | WebSocket
### WebSocket Protocol
Connect: `wss://{space}.rspace.online/?mode=automerge&token=JWT`
| Message Type | Direction | Payload |
|--------------|-----------|---------|
| `sync` | Bidirectional | Automerge binary sync frame |
| `snapshot` | Server→Client | Full JSON state (fallback mode) |
| `presence` | Bidirectional | `{ userId, cursor, selection }` |
| `ping` | Client→Server | Keep-alive (30s interval) |
| `pong` | Server→Client | Keep-alive response |
| `error` | Server→Client | `{ code, message }` |
### REST API
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/health` | None | Health check with DB connectivity |
| GET | `/api/communities/:slug` | Optional | Get community metadata |
| POST | `/api/communities/:slug/shapes/:id` | Service | Update shape from module callback |
### Shape Update API (NEW — bidirectional sync)
```json
POST /api/communities/{slug}/shapes/{shapeId}
Authorization: Bearer <service-jwt>
{
"title": "Updated Note Title",
"status": "PASSED",
"fields": { ... }
}
```
---
## rCal — Calendar & Scheduling
**Base URL**: `https://{space}.rcal.online`
**Tech**: Next.js App Router | PostgreSQL (Prisma)
### Events
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/events` | Optional | List events (filter by date range, source) |
| POST | `/api/events` | PARTICIPANT | Create event |
| GET | `/api/events/:id` | Optional | Get event details |
| PATCH | `/api/events/:id` | Author/MOD | Update event |
| DELETE | `/api/events/:id` | Author/MOD | Delete event |
### Calendar Sources
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/sources` | Bearer | List calendar sources (Google, iCal, etc.) |
| POST | `/api/sources` | PARTICIPANT | Add calendar source |
| GET | `/api/sources/:id` | Bearer | Get source details |
| PATCH | `/api/sources/:id` | Owner | Update source config |
| DELETE | `/api/sources/:id` | Owner | Remove source |
### Context & Integration
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/context/:tool` | Bearer | Get events relevant to an r-tool context |
| GET | `/api/lunar` | None | Get lunar phase data for date range |
---
## rMaps — Spatial Intelligence
**Base URL**: `https://{space}.rmaps.online`
**Tech**: Next.js App Router | PostgreSQL (Prisma) | MapLibre GL
### Routing
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/api/routing` | Optional | Calculate route (origin, dest, mode) |
Request body:
```json
{
"origin": { "lat": 52.52, "lng": 13.405 },
"destination": { "lat": 52.51, "lng": 13.39 },
"mode": "walking",
"indoor": false
}
```
### Indoor Maps (c3nav)
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/c3nav/:event` | None | Get indoor map metadata for event |
| GET | `/api/c3nav/tiles/:event/:level/:z/:x/:y` | None | Tile server for indoor maps |
### WebSocket (Real-time Location Sharing)
Connect: `wss://{room}.rmaps.online/ws?token=JWT`
| Message Type | Direction | Payload |
|--------------|-----------|---------|
| `location` | Client→Server | `{ lat, lng, accuracy, privacy }` |
| `locations` | Server→Client | `{ participants: [...] }` |
| `waypoint` | Bidirectional | `{ id, name, lat, lng, emoji }` |
Privacy modes: `exact`, `approximate`, `area`, `ghost`
---
## rFiles — Secure File Sharing
**Base URL**: `https://{space}.rfiles.online`
**Tech**: Django REST Framework | PostgreSQL | S3-compatible storage
### Media Files
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/v1/media/` | Bearer | List files (filter by mime, tags, space) |
| POST | `/api/v1/media/` | PARTICIPANT | Upload file |
| GET | `/api/v1/media/:id/` | Bearer | Get file metadata |
| PATCH | `/api/v1/media/:id/` | Author/MOD | Update metadata |
| DELETE | `/api/v1/media/:id/` | Author/ADMIN | Delete file |
| POST | `/api/v1/media/:id/share/` | Author | Create share link |
| GET | `/api/v1/media/:id/shares/` | Author | List shares for file |
| GET | `/api/v1/media/:id/access_logs/` | Author | Access history (last 100) |
### Public Shares
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/s/:token/` | None | Download shared file |
| GET | `/s/:token/download/` | None | Explicit download |
| GET | `/s/:token/info/` | None | Get share info (no download) |
| POST | `/s/:token/verify/` | None | Verify password-protected share |
### Share Management
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/v1/shares/` | Bearer | List user's shares |
| POST | `/api/v1/shares/:id/revoke/` | Author | Revoke share link |
| POST | `/api/v1/shares/:id/set_password/` | Author | Add password to share |
| POST | `/api/v1/shares/:id/remove_password/` | Author | Remove password |
### Direct Upload
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/api/upload/` | Bearer | Direct file upload (FormData or pre-signed) |
---
## rFunds — Funding Flows
**Base URL**: `https://{space}.rfunds.online`
**Tech**: Next.js | React Flow | Automerge CRDT
rFunds is primarily client-side (Automerge + localStorage). The flow diagrams are stored in the rSpace Automerge document as `folk-budget` and `demo-expense` shapes.
### Planned Server API
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/api/flows` | PARTICIPANT | Save flow diagram to server |
| GET | `/api/flows/:id` | VIEWER | Get flow diagram |
| POST | `/api/campaigns` | PARTICIPANT | Create crowdfunding campaign |
| POST | `/api/campaigns/:id/contribute` | PARTICIPANT | Contribute to campaign |
---
## rTube — Community Video
**Base URL**: `https://{space}.rtube.online`
**Tech**: Hono.js on Bun | Cloudflare R2
### Videos
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/api/upload` | PARTICIPANT | Upload video file |
| GET | `/api/videos` | VIEWER | List videos in space |
| GET | `/api/videos/:id` | VIEWER | Get video metadata |
| GET | `/api/videos/:id/stream` | VIEWER | Stream video (HTTP range requests) |
### Live Streaming (Planned)
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/api/streams` | PARTICIPANT | Start RTMP ingest stream |
| GET | `/api/streams/:id/hls` | VIEWER | Get HLS playlist for live stream |
---
## rMail — Secure Messaging
**Base URL**: `https://{space}.rmail.online`
**Tech**: Next.js App Router | PostgreSQL (Prisma) | IMAP
### Mailboxes
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/mailboxes` | Bearer | List user's mailboxes |
| POST | `/api/mailboxes` | ADMIN | Create mailbox (IMAP/SMTP config) |
| GET | `/api/mailboxes/:slug/members` | Bearer | List mailbox members |
| POST | `/api/mailboxes/:slug/members` | ADMIN | Add member |
| DELETE | `/api/mailboxes/:slug/members/:did` | ADMIN | Remove member |
### Messages
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/mailboxes/:slug/messages` | Bearer | List messages (IMAP sync) |
| GET | `/api/mailboxes/:slug/threads/:threadId` | Bearer | Get email thread |
| PATCH | `/api/mailboxes/:slug/threads/:threadId` | Bearer | Update thread (read, folder) |
| POST | `/api/mailboxes/:slug/threads/:threadId/comments` | Bearer | Add comment |
### Multi-Sig Approvals
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/approvals` | Bearer | List pending approvals |
| POST | `/api/approvals` | PARTICIPANT | Create email approval draft |
| GET | `/api/approvals/:id` | Bearer | Get approval with signatures |
| POST | `/api/approvals/:id/sign` | Bearer | Sign approval (add signature) |
### Workspaces
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/workspaces` | Bearer | List workspaces |
| POST | `/api/workspaces` | Bearer | Create workspace |
| GET | `/api/workspaces/:slug/mailboxes` | Bearer | List mailboxes in workspace |
---
## rTrips — Trip Planning
**Base URL**: `https://{space}.rtrips.online`
**Tech**: Next.js App Router | PostgreSQL (Prisma) | Gemini AI
### Trips
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/trips` | Bearer | List trips |
| POST | `/api/trips` | PARTICIPANT | Create trip from parsed text |
| GET | `/api/trips/:id` | Bearer | Get trip with all entities |
| PATCH | `/api/trips/:id` | Author/MOD | Update trip |
| DELETE | `/api/trips/:id` | Author/ADMIN | Delete trip |
| GET | `/api/trips/by-slug/:slug` | Bearer | Get trip by slug |
| POST | `/api/trips/parse` | Bearer | AI-parse trip text → structured data |
### Destinations
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/trips/:id/destinations` | Bearer | List destinations |
| POST | `/api/trips/:id/destinations` | PARTICIPANT | Add destination |
| PATCH | `/api/trips/:id/destinations/:destId` | Author/MOD | Update destination |
| DELETE | `/api/trips/:id/destinations/:destId` | Author/MOD | Remove destination |
### Itinerary
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/trips/:id/itinerary` | Bearer | List itinerary items |
| POST | `/api/trips/:id/itinerary` | PARTICIPANT | Add item |
| PATCH | `/api/trips/:id/itinerary/:itemId` | Author/MOD | Update item |
| DELETE | `/api/trips/:id/itinerary/:itemId` | Author/MOD | Remove item |
### Bookings
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/trips/:id/bookings` | Bearer | List bookings |
| POST | `/api/trips/:id/bookings` | PARTICIPANT | Add booking |
| PATCH | `/api/trips/:id/bookings/:bookingId` | Author/MOD | Update booking |
| DELETE | `/api/trips/:id/bookings/:bookingId` | Author/MOD | Cancel booking |
### Expenses
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/trips/:id/expenses` | Bearer | List expenses |
| POST | `/api/trips/:id/expenses` | PARTICIPANT | Add expense |
| PATCH | `/api/trips/:id/expenses/:expenseId` | Author/MOD | Update expense |
| DELETE | `/api/trips/:id/expenses/:expenseId` | Author/MOD | Remove expense |
### Packing Lists
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/trips/:id/packing` | Bearer | Get packing list |
| POST | `/api/trips/:id/packing` | PARTICIPANT | Add item |
| PATCH | `/api/trips/:id/packing/:itemId` | Bearer | Check off item |
| DELETE | `/api/trips/:id/packing/:itemId` | Author/MOD | Remove item |
### Canvas & Cross-Module
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/trips/:id/canvas` | Bearer | Get linked canvas shape data |
| POST | `/api/trips/:id/sync` | Service | Receive canvas shape updates |
| POST | `/api/proxy/rvote` | Bearer | Proxy to rVote (in-trip voting) |
| POST | `/api/proxy/rnotes` | Bearer | Proxy to rNotes (shared trip notes) |
---
## rNetwork — Social Graph
**Base URL**: `https://{space}.rnetwork.online`
**Tech**: Next.js | Automerge CRDT | Force-directed graph
rNetwork uses Automerge CRDT for the graph document. No traditional REST API — state syncs via WebSocket.
### WebSocket Protocol
Connect: `wss://{space}.rnetwork.online/ws?token=JWT`
| Message Type | Direction | Payload |
|--------------|-----------|---------|
| `sync` | Bidirectional | Automerge binary sync frame |
| `snapshot` | Server→Client | Full graph JSON |
### Graph Document Schema
```typescript
{
meta: { name, slug, visibility, ownerDID },
nodes: {
[id]: { type: "person"|"org"|"role", name, metadata }
},
edges: {
[id]: { source, target, type, label, metadata }
}
}
```
---
## rWallet — Treasury
**Base URL**: `https://{space}.rwallet.online`
**Tech**: Next.js | Gnosis Safe SDK
Currently static frontend. Planned API:
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/safes` | Bearer | List connected Safe wallets |
| GET | `/api/safes/:address/balances` | VIEWER | Get token balances |
| GET | `/api/safes/:address/transactions` | VIEWER | List transactions |
| POST | `/api/safes/:address/propose` | PARTICIPANT | Propose new transaction |
| POST | `/api/safes/:address/sign/:txHash` | PARTICIPANT | Sign pending transaction |
---
## EncryptID SDK (npm: `@encryptid/sdk`)
### Client-Side
```typescript
import { EncryptIDClient } from '@encryptid/sdk/client';
const client = new EncryptIDClient('https://encryptid.jeffemmett.com');
// Register
await client.register(username);
// Authenticate
const { token, claims } = await client.authenticate();
// Share token across r*.online modules
await client.shareTokenAcrossModules(token, [
'rvote.online',
'rnotes.online',
'rmaps.online',
]);
```
### Server-Side (Hono)
```typescript
import {
encryptIDAuth,
encryptIDSpaceRoleAuth,
} from '@encryptid/sdk/server/middleware/hono';
// Simple auth (require valid token)
app.use('/api/*', encryptIDAuth({ serverUrl }));
// Space + role auth (combined)
app.use('/api/*', encryptIDSpaceRoleAuth({ serverUrl, lookupMembership }));
// Access context
app.get('/api/data', (c) => {
const claims = c.get('encryptid'); // EncryptIDClaims
const role = c.get('spaceRole'); // ResolvedRole
return c.json({ user: claims.did, role: role.role });
});
```
### Server-Side (Next.js)
```typescript
import {
checkSpaceRole,
withSpaceRole,
} from '@encryptid/sdk/server/middleware/nextjs';
// In a route handler
export async function GET(req) {
const { claims, role } = await checkSpaceRole(req, {
serverUrl: 'https://encryptid.jeffemmett.com',
spaceSlug: 'crypto',
});
if (!hasCapability(role.role, 'view_proposals', RVOTE_PERMISSIONS)) {
return Response.json({ error: 'Forbidden' }, { status: 403 });
}
// ... handle request
}
```
### Server-Side (Python/Django)
```python
from encryptid.roles import SpaceRole, has_capability, RFILES_PERMISSIONS
# Check if user can upload files
if has_capability(user_role, 'upload_file', RFILES_PERMISSIONS):
# allow upload
```
### Capability Maps
Import per-module permission maps:
```typescript
import { RVOTE_PERMISSIONS } from '@encryptid/sdk/types/modules';
import { RSPACE_PERMISSIONS } from '@encryptid/sdk/types/modules';
import { RNOTES_PERMISSIONS } from '@encryptid/sdk/types/modules';
import { RFUNDS_PERMISSIONS } from '@encryptid/sdk/types/modules';
import { RMAPS_PERMISSIONS } from '@encryptid/sdk/types/modules';
import { RTUBE_PERMISSIONS } from '@encryptid/sdk/types/modules';
```
---
## WebSocket Protocols Summary
| Module | URL Pattern | Protocol | Auth |
|--------|-------------|----------|------|
| rSpace | `wss://{slug}.rspace.online/` | Automerge binary sync | JWT (query/subprotocol) |
| rNetwork | `wss://{slug}.rnetwork.online/ws` | Automerge binary sync | JWT (query/subprotocol) |
| rMaps | `wss://{room}.rmaps.online/ws` | JSON location/waypoint | JWT (query) |
---
## Cross-Module Integration
### Canvas Shape Sync (module → canvas)
When a module updates an entity bound to a canvas shape:
```
POST https://{slug}.rspace.online/api/communities/{slug}/shapes/{shapeId}
Authorization: Bearer <service-jwt>
Content-Type: application/json
{
"title": "Updated Title",
"status": "PASSED"
}
```
### Canvas Shape Sync (canvas → module)
When rSpace updates a shape bound to a module entity, it calls the module's sync endpoint:
```
POST https://{slug}.rnotes.online/api/sync
POST https://{slug}.rtrips.online/api/trips/{id}/sync
```
### Module Proxying
rTrips demonstrates the proxy pattern for embedded module access:
```
POST /api/proxy/rvote → proxied to rvote.online
POST /api/proxy/rnotes → proxied to rnotes.online
```