Skip to content

RPC Gateway

The RPC Gateway is a public-facing service that exposes Flowstate’s backend functionality via Connect RPC, a modern HTTP-based RPC framework compatible with gRPC. It’s designed for desktop and mobile clients that need structured, type-safe API access.

The RPC Gateway runs as a separate service but shares the same codebase as the backend REST API. It only exposes the /rpc endpoint and /health check.

Key Differences from REST API:

FeatureREST APIRPC Gateway
TransportHTTP/JSONConnect RPC (HTTP/Protobuf or JSON)
AuthenticationCookie-based JWTBearer token in Authorization header
ExposureInternal only (via proxy)Public-facing
ClientsWeb applicationDesktop, mobile, CLI tools
CORSRestricted originsAll origins allowed
https://rpc.goflowstate.com/rpc

The RPC Gateway uses Bearer token authentication with a three-tier system:

These methods can be called without any authentication:

  • AuthService.RequestLogin - Request magic link
  • AuthService.PollLoginStatus - Poll for login completion
  • AuthService.GetAuthorizeInfo - Get authorization info
  • AuthService.AuthorizeLogin - Complete login flow
  • AuthService.ConfirmDeletion - Confirm account deletion
  • AuthService.Ping - Health check
  • OfficesService.GetInviteInfo - Get office invite details
  • AuthService.GetConfig - Get public configuration

These methods work with or without authentication, returning different data based on auth status:

  • RoomsService.ListRooms - Public rooms (no auth) vs user’s rooms (auth)
  • OfficesService.ListOffices - Public offices (no auth) vs user’s offices (auth)

All other methods require a valid Bearer token in the Authorization header:

Authorization: Bearer <jwt_token>

Example Request:

Terminal window
curl -X POST https://rpc.goflowstate.com/rpc/flowstate.rooms.v1.RoomsService/GetRoom \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{"roomId": "room_abc123"}'

The gateway uses Connect RPC, which supports:

  • HTTP/1.1 and HTTP/2 - Works with standard HTTP infrastructure
  • JSON and Protobuf - Flexible serialization (JSON for debugging, Protobuf for performance)
  • Streaming - Server streaming, client streaming, bidirectional streaming
  • Type safety - Generated TypeScript/Swift/Kotlin clients from Protocol Buffer definitions

The gateway accepts both JSON and Protobuf:

Content-Type: application/json (human-readable, debugging)
Content-Type: application/proto (binary, production)

The gateway exposes 9 services with 53 RPC methods:

ServiceMethodsDescription
AuthService9Login, polling, session management, configuration
UsersService4User profiles, display info, search
RoomsService5Room CRUD, listing, membership
SparksService6Spark creation, updates, deletion, positioning
OfficesService10+Office management, invites, membership, settings
ShelvesService6Shelf and link management, ordering
CanvasService5Position updates, layout computation, collision detection
PassportService8+Profile management, CV import, skills, experience
AgoraService2Video/voice token generation, configuration

Handles authentication and session management.

Methods:

service AuthService {
rpc RequestLogin(RequestLoginRequest) returns (RequestLoginResponse);
rpc PollLoginStatus(PollLoginStatusRequest) returns (PollLoginStatusResponse);
rpc GetAuthorizeInfo(GetAuthorizeInfoRequest) returns (GetAuthorizeInfoResponse);
rpc AuthorizeLogin(AuthorizeLoginRequest) returns (AuthorizeLoginResponse);
rpc GetMe(GetMeRequest) returns (GetMeResponse);
rpc RefreshToken(RefreshTokenRequest) returns (RefreshTokenResponse);
rpc Logout(LogoutRequest) returns (LogoutResponse);
rpc RequestDeletion(RequestDeletionRequest) returns (RequestDeletionResponse);
rpc ConfirmDeletion(ConfirmDeletionRequest) returns (ConfirmDeletionResponse);
rpc GetConfig(GetConfigRequest) returns (GetConfigResponse);
rpc Ping(PingRequest) returns (PingResponse);
}

Example: Request Login

// Request
{
"email": "[email protected]"
}
// Response
{
"pollToken": "poll_abc123",
"expiresAt": "2026-02-28T10:40:00.000Z"
}

Example: Poll Login Status

// Request
{
"pollToken": "poll_abc123"
}
// Response (pending)
{
"status": "PENDING"
}
// Response (completed)
{
"status": "COMPLETED",
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "refresh_xyz789",
"expiresAt": "2026-03-30T10:35:00.000Z"
}

Manages rooms and room membership.

Methods:

service RoomsService {
rpc ListRooms(ListRoomsRequest) returns (ListRoomsResponse);
rpc GetRoom(GetRoomRequest) returns (GetRoomResponse);
rpc CreateRoom(CreateRoomRequest) returns (CreateRoomResponse);
rpc UpdateRoom(UpdateRoomRequest) returns (UpdateRoomResponse);
rpc DeleteRoom(DeleteRoomRequest) returns (DeleteRoomResponse);
}

Example: Create Room

// Request
{
"name": "Design Team",
"description": "Collaborative design workspace",
"isPublic": false
}
// Response
{
"room": {
"id": "room_abc123",
"name": "Design Team",
"description": "Collaborative design workspace",
"isPublic": false,
"createdBy": "user_xyz789",
"createdAt": "2026-02-28T10:30:00.000Z",
"updatedAt": "2026-02-28T10:30:00.000Z"
}
}

Manages sparks (content tiles on the canvas).

Methods:

service SparksService {
rpc ListSparks(ListSparksRequest) returns (ListSparksResponse);
rpc GetSpark(GetSparkRequest) returns (GetSparkResponse);
rpc CreateSpark(CreateSparkRequest) returns (CreateSparkResponse);
rpc UpdateSpark(UpdateSparkRequest) returns (UpdateSparkResponse);
rpc DeleteSpark(DeleteSparkRequest) returns (DeleteSparkResponse);
rpc MoveSpark(MoveSparkRequest) returns (MoveSparkResponse);
}

Example: Create Spark

// Request
{
"title": "Project Kickoff",
"content": "Let's discuss the roadmap for Q2",
"roomId": "room_abc123",
"x": 100,
"y": 200
}
// Response
{
"spark": {
"id": "spark_def456",
"title": "Project Kickoff",
"content": "Let's discuss the roadmap for Q2",
"roomId": "room_abc123",
"x": 100,
"y": 200,
"createdBy": "user_xyz789",
"createdAt": "2026-02-28T10:35:00.000Z"
}
}

Manages offices, invites, and membership.

Methods:

service OfficesService {
rpc ListOffices(ListOfficesRequest) returns (ListOfficesResponse);
rpc GetOffice(GetOfficeRequest) returns (GetOfficeResponse);
rpc CreateOffice(CreateOfficeRequest) returns (CreateOfficeResponse);
rpc UpdateOffice(UpdateOfficeRequest) returns (UpdateOfficeResponse);
rpc DeleteOffice(DeleteOfficeRequest) returns (DeleteOfficeResponse);
rpc InviteToOffice(InviteToOfficeRequest) returns (InviteToOfficeResponse);
rpc GetInviteInfo(GetInviteInfoRequest) returns (GetInviteInfoResponse);
rpc AcceptInvite(AcceptInviteRequest) returns (AcceptInviteResponse);
rpc ListMembers(ListMembersRequest) returns (ListMembersResponse);
rpc RemoveMember(RemoveMemberRequest) returns (RemoveMemberResponse);
rpc UpdateMemberRole(UpdateMemberRoleRequest) returns (UpdateMemberRoleResponse);
}

Handles canvas layout and positioning.

Methods:

service CanvasService {
rpc UpdatePosition(UpdatePositionRequest) returns (UpdatePositionResponse);
rpc BatchUpdatePositions(BatchUpdatePositionsRequest) returns (BatchUpdatePositionsResponse);
rpc ComputeLayout(ComputeLayoutRequest) returns (ComputeLayoutResponse);
rpc ValidateLayout(ValidateLayoutRequest) returns (ValidateLayoutResponse);
rpc GetCanvasState(GetCanvasStateRequest) returns (GetCanvasStateResponse);
}

Example: Update Position

// Request
{
"tileId": "spark_abc123",
"x": 500,
"y": 600
}
// Response
{
"position": {
"tileId": "spark_abc123",
"x": 500,
"y": 600,
"snapped": true,
"collisionDetected": false
}
}

Manages user profiles and professional information.

Methods:

service PassportService {
rpc GetPassport(GetPassportRequest) returns (GetPassportResponse);
rpc UpdatePassport(UpdatePassportRequest) returns (UpdatePassportResponse);
rpc ImportCV(ImportCVRequest) returns (ImportCVResponse);
rpc AddSkill(AddSkillRequest) returns (AddSkillResponse);
rpc RemoveSkill(RemoveSkillRequest) returns (RemoveSkillResponse);
rpc AddExperience(AddExperienceRequest) returns (AddExperienceResponse);
rpc UpdateExperience(UpdateExperienceRequest) returns (UpdateExperienceResponse);
rpc DeleteExperience(DeleteExperienceRequest) returns (DeleteExperienceResponse);
}

Generates tokens for Agora video/voice chat.

Methods:

service AgoraService {
rpc GenerateToken(GenerateTokenRequest) returns (GenerateTokenResponse);
rpc GetConfig(GetConfigRequest) returns (GetConfigResponse);
}

Example: Generate Token

// Request
{
"channelName": "office_abc123",
"uid": "user_xyz789",
"role": "PUBLISHER"
}
// Response
{
"token": "006abc123def456...",
"expiresAt": "2026-02-28T11:30:00.000Z",
"appId": "a1b2c3d4e5f6g7h8"
}

All RPC methods are defined using Protocol Buffers. Services are namespaced under flowstate.<domain>.v1 (e.g., flowstate.rooms.v1.RoomsService).

These definitions are used to generate type-safe clients for TypeScript, Swift, Kotlin, and other languages.

The desktop app uses a generated TypeScript client:

import { createPromiseClient } from '@connectrpc/connect';
import { createConnectTransport } from '@connectrpc/connect-web';
import { RoomsService } from '@/generated/flowstate/rooms/v1/rooms_connect';
const transport = createConnectTransport({
baseUrl: 'https://rpc.goflowstate.com',
interceptors: [authInterceptor], // Adds Bearer token
});
const client = createPromiseClient(RoomsService, transport);
const response = await client.listRooms({ page: 1, limit: 20 });
console.log(response.rooms);

RPC errors follow the Connect error model with standard error codes:

CodeDescription
CANCELLEDRequest cancelled by client
UNKNOWNUnknown error
INVALID_ARGUMENTInvalid request parameters
DEADLINE_EXCEEDEDRequest timeout
NOT_FOUNDResource not found
ALREADY_EXISTSResource already exists
PERMISSION_DENIEDInsufficient permissions
UNAUTHENTICATEDMissing or invalid authentication
RESOURCE_EXHAUSTEDRate limit exceeded
FAILED_PRECONDITIONPrecondition not met
ABORTEDOperation aborted
OUT_OF_RANGEValue out of valid range
UNIMPLEMENTEDMethod not implemented
INTERNALInternal server error
UNAVAILABLEService unavailable
DATA_LOSSData loss or corruption

Example Error Response:

{
"code": "INVALID_ARGUMENT",
"message": "Invalid room name: must be between 1 and 100 characters",
"details": [
{
"@type": "type.googleapis.com/google.rpc.BadRequest",
"fieldViolations": [
{
"field": "name",
"description": "String must contain at least 1 character(s)"
}
]
}
]
}

The RPC Gateway and REST API share the same business logic layer. Both call the same service functions, just with different transport layers:

REST API (Express routes) ──┐
├──> Service Layer (business logic) ──> Database
RPC Gateway (Connect RPCs) ──┘

This ensures consistency between the web and desktop applications.

GET /health

Returns the gateway’s health status:

{
"status": "healthy",
"timestamp": "2026-02-28T10:30:00.000Z",
"services": {
"database": { "status": "healthy", "latency": 12 },
"redis": { "status": "healthy", "latency": 3 }
}
}

The gateway enforces rate limits per IP address:

  • Default: 100 requests per minute
  • Auth methods: 10 requests per minute
  • Token generation: 20 requests per minute

Rate limit errors return RESOURCE_EXHAUSTED with a Retry-After header.

The RPC Gateway allows all origins (*) since it’s a public API secured by Bearer tokens. Credentials are not used (no cookies).

  1. Use generated clients instead of manual HTTP requests (type safety, error handling)
  2. Implement token refresh before tokens expire (check expiresAt field)
  3. Handle errors gracefully using Connect error codes
  4. Use Protobuf in production for smaller payloads and better performance
  5. Cache tokens securely (never store in localStorage, use secure storage APIs)
  6. Implement exponential backoff for retries on transient errors
  7. Monitor rate limits and implement client-side throttling if needed