Last updated 2026-06-29
Using the API
This page is the conceptual guide to calling the workspaces API: the transport, the service credential, the request fields every RPC shares, and the error model. For the exhaustive, per-RPC reference — every request and response message, generated live from the proto — use the Live API Reference linked below.
/workspace/openapi/workspace-v1.yaml.
The spec is generated from the proto by make openapi, so it is
always in sync with the running service.
Two views, two audiences: the Live API Reference
above is the HTTP/JSON surface (with a try-it console),
while the Proto Reference is a generated HTML
view of the protobuf contract itself — every service,
message, enum, and the raw .proto — which is what you generate
a gRPC/Connect client from. The canonical, versioned schema and generated
SDKs are published to the
Buf Schema Registry
(buf.build/elloloop/workspace).
Transport
Every endpoint is a Connect RPC. The
simplest way to call it is plain HTTP POST with a JSON body
— no client library required, just curl. The same handlers also
speak gRPC and gRPC-Web, so generated
Connect or gRPC clients work unchanged against the same port.
The URL path is fixed by the proto package and is the same for every protocol:
/workspace.v1.<Service>/<Method>
The proto package is workspace.v1, so a call to
ListWorkspaces on WorkspaceService is a POST to
/workspace.v1.WorkspaceService/ListWorkspaces. By default the
service listens on port 8080
(GATEWAY_CONNECT_PORT).
Authentication: the service credential
Workspaces is an internal service-to-service API. The caller is a trusted product backend, not a browser and not an end user. It authenticates with a shared service token sent as a bearer header:
Authorization: Bearer <service-token>
Valid tokens come from the GATEWAY_SERVICE_AUTH_TOKENS
environment variable (comma-separated). A missing or wrong credential is
rejected with HTTP 401, which Connect surfaces to clients as
code Unauthenticated. If GATEWAY_SERVICE_AUTH_TOKENS
is empty the requirement is disabled and every caller is
trusted — only appropriate behind a service mesh, mTLS, or a private network
(see Security & Service Auth).
Shared request fields
Three fields recur across the API. They carry the user identities and the isolation shard — the service trusts them because it already trusted the caller's service token.
| Field | On | Meaning |
|---|---|---|
acting_user_id | Management RPCs (workspace, group, invitation writes/reads) | Required. The user the action is authorized as.
Omitting it returns InvalidArgument.
|
subject_user_id | Check, ReadRelationTuples | The user being tested — pure data, independent of the caller or the acting user. |
project_id | Every RPC |
Optional isolation shard. Empty falls back to
GATEWAY_DEFAULT_PROJECT_ID (default "default").
See Projects & Multi-Tenancy.
|
Two worked calls
List the workspaces Alice belongs to (auto-provisions her personal workspace on first call):
curl -X POST http://localhost:8080/workspace.v1.WorkspaceService/ListWorkspaces \ -H "Authorization: Bearer $WS_SERVICE_TOKEN" \ -H "Content-Type: application/json" \ -d '{"acting_user_id":"alice"}'Ask whether Bob is an admin of workspace W:
curl -X POST http://localhost:8080/workspace.v1.AuthzService/Check \ -H "Authorization: Bearer $WS_SERVICE_TOKEN" \ -H "Content-Type: application/json" \ -d '{"namespace":"workspace","object_id":"W","relation":"admin","subject_user_id":"bob"}'
The response is JSON shaped like the proto response message — for
Check, {"allowed": true}.
Error model
Errors use standard
Connect error codes.
Over HTTP/JSON each maps to an HTTP status and a body with a
code and message. The ones you will see:
| Connect code | HTTP | When |
|---|---|---|
unauthenticated | 401 | Missing or wrong service token in the Authorization header. |
invalid_argument | 400 |
A required field is missing or malformed — e.g. no
acting_user_id, an empty display_name, or a
role of ROLE_OWNER on AddMember.
|
permission_denied | 403 | The acting user is not authorized to perform the action on the target. |
not_found | 404 | The referenced workspace, group, member, or invitation does not exist in the project. |
failed_precondition | 412 | The request conflicts with the resource's state — e.g. adding a member to a personal workspace, or accepting an already-consumed invitation. |
The three services
Workspaces exposes three Connect services. The Live API Reference documents each request and response field; this table is the map.
| Service | Owns | RPCs |
|---|---|---|
WorkspaceService | Workspaces, members, invitations | CreateWorkspace, GetWorkspace,
ListWorkspaces, UpdateWorkspace,
DeleteWorkspace, AddMember,
UpdateMemberRole, RemoveMember,
ListMembers, CreateInvitation,
AcceptInvitation, ListInvitations,
RevokeInvitation |
GroupService | Reusable, nestable membership sets | CreateGroup, GetGroup,
ListGroups, DeleteGroup,
AddGroupMember, RemoveGroupMember,
ListGroupMembers |
AuthzService | The relation-tuple engine | WriteRelationTuples, ReadRelationTuples,
Check, Expand |
ListWorkspaces auto-provisions the caller's personal workspace
on first call. CreateInvitation returns a one-time token;
ListInvitations never echoes it. ReadRelationTuples
is a raw store read with no rewrite evaluation — use Check for
an authorization decision and Expand for the resolved userset
tree (see Check & Expand).
Generated clients
Go services should not hand-write requests. The repository ships generated
Connect-Go clients under
gen/go
— the message types in
gen/go/workspace/v1 and the client/handler interfaces in
gen/go/workspace/v1/workspacev1connect. Import them from the
module github.com/elloloop/workspace and call typed methods
instead of building JSON by hand; the bearer header is set the same way:
import ( workspacev1 "github.com/elloloop/workspace/gen/go/workspace/v1" "github.com/elloloop/workspace/gen/go/workspace/v1/workspacev1connect")
client := workspacev1connect.NewWorkspaceServiceClient( http.DefaultClient, "http://localhost:8080",)
req := connect.NewRequest(&workspacev1.ListWorkspacesRequest{ActingUserId: "alice"})req.Header().Set("Authorization", "Bearer "+serviceToken)res, err := client.ListWorkspaces(ctx, req)For other languages, generate a Connect or gRPC client from the same OpenAPI/proto and point it at the same URL and header.