This document provides an overview of the REST API endpoints. For detailed schemas, see the OpenAPI specifications in the source code.
- Ganymede (API):
https://ganymede.{env}.{domain} - Gateway (Collab):
https://gateway.{org-id}.{env}.{domain}(per organization)
All API requests require authentication via session cookies or JWT tokens.
Used for web application requests:
Cookie: connect.sid=s%3A...Obtained via login endpoints:
POST /auth/local/loginGET /auth/{provider}/callback(OAuth)POST /auth/totp/verifyGET /auth/magic-link/{token}
The system uses different JWT token types for different purposes:
-
TJwtUser(access_token/refresh_token): User authentication tokens- Contains:
user.id,user.username,client_id,scope[], optionalproject_id - Used for: Human user API access, collaboration events, WebSocket connections
- Contains:
-
TJwtUserContainer(user_container_token): Container authentication tokens- Contains:
project_id,user_container_id,scope - Used for: User container server authentication
- Generated by: Ganymede (via
POST /gateway/tokens/user-container), not by gateway
- Contains:
-
TJwtOrganization(organization_token): Organization-level tokens- Contains:
organization_id,gateway_id,scope - Used for: Gateway-to-Ganymede communication
- Contains:
-
TJwtGateway(gateway_token): Gateway-level tokens- Contains:
gateway_id,scope - Used for: Gateway container authentication
- Contains:
All JWT tokens should be sent in the Authorization header:
Authorization: Bearer eyJhbGc...Or with token prefix for user tokens:
Authorization: token eyJhbGc...The gateway sometime uses scope-based authorization for fine-grained access control in addition to permissions managed by gateway. Scopes can include template variables that are resolved at runtime:
{org_id}- Replaced with gateway's organization ID${params.key}- Replaced withreq.params[key]${body.key}- Replaced withreq.body[key]${query.key}- Replaced withreq.query[key]${jwt.key}- Replaced withreq.jwt[key]
Examples:
org:{org_id}:connect-vpn- Organization-specific VPN access (e.g.,org:550e8400-e29b-41d4-a716-446655440000:connect-vpn)
| Method | Endpoint | Description |
|---|---|---|
POST |
/auth/local/signup |
Create account with email/password |
POST |
/auth/local/login |
Login with email/password |
POST |
/auth/logout |
Logout current user |
GET |
/auth/github |
OAuth login with GitHub |
GET |
/auth/gitlab |
OAuth login with GitLab |
GET |
/auth/discord |
OAuth login with Discord |
GET |
/auth/linkedin |
OAuth login with LinkedIn |
POST |
/auth/totp/setup |
Setup TOTP (2FA) |
POST |
/auth/totp/verify |
Verify TOTP code |
POST |
/auth/magic-link/send |
Send magic link email |
GET |
/auth/magic-link/{token} |
Login via magic link |
| Method | Endpoint | Description |
|---|---|---|
GET |
/users/me |
Get current user info |
GET |
/users/{id} |
Get user by ID |
GET |
/users/search?q={query} |
Search users by email/username |
| Method | Endpoint | Description |
|---|---|---|
GET |
/organizations |
List user's organizations |
POST |
/organizations |
Create new organization |
GET |
/organizations/{id} |
Get organization details |
DELETE |
/organizations/{id} |
Delete organization |
GET |
/organizations/{id}/members |
List organization members |
POST |
/organizations/{id}/members |
Add member to organization |
DELETE |
/organizations/{id}/members/{user_id} |
Remove member |
PUT |
/organizations/{id}/members/{user_id}/role |
Update member role |
| Method | Endpoint | Description |
|---|---|---|
GET |
/projects |
List user's projects |
GET |
/organizations/{org_id}/projects |
List organization's projects |
POST |
/projects |
Create new project |
GET |
/projects/{id} |
Get project details (includes gateway) |
DELETE |
/projects/{id} |
Delete project |
GET |
/projects/{id}/members |
List project members |
POST |
/projects/{id}/members |
Add member to project |
DELETE |
/projects/{id}/members/{user_id} |
Remove member |
Ganymede provides OAuth2 for user authentication only. This is used by the frontend to obtain user access tokens via the global app-main-client-id client.
| Method | Endpoint | Description |
|---|---|---|
GET |
/oauth/authorize |
OAuth2 authorization endpoint |
POST |
/oauth/authorize |
OAuth2 authorization endpoint (POST variant) |
POST |
/oauth/token |
Exchange code for token or refresh token |
GET |
/oauth/public-key |
Get JWT public key |
Authentication Details:
GET /oauth/authorize: User must be authenticated via session cookie. Used for OAuth2 authorization code flow where the user grants permission to the frontend application. Only supports the globalapp-main-client-idclient.POST /oauth/token: No authentication required. Uses client credentials (client_id, client_secret) from request body. Supportsauthorization_codeandrefresh_tokengrant types. Only supports the globalapp-main-client-idclient.GET /oauth/public-key: No authentication required. Returns the public key used to verify JWT tokens issued by Ganymede.
Note: Ganymede OAuth is only for user authentication. Container applications use their own OAuth clients via Gateway OAuth provider (see Gateway API section).
| Method | Endpoint | Description |
|---|---|---|
POST |
/gateway/start |
Start gateway for organization |
POST |
/gateway/config |
Get gateway config (called by gateway) |
POST |
/gateway/ready |
Signal gateway is ready |
POST |
/gateway/stop |
Stop gateway |
POST |
/gateway/tokens/scoped |
Generate project-scoped JWT token (gateway calls this) |
Gateway endpoints are accessed via WebSocket for collaboration data, and via HTTP for specific operations.
Endpoint: wss://gateway.{org-id}.{env}.{domain}/collab/{room_id}?token=...
Authentication: TJwtUser (access_token only)
The WebSocket connection requires a user access token in the query parameter. The token must have a project_id (either in the JWT payload or inferred from the room_id), and the user must have access to that project (member, admin, or org admin/owner).
Events sent to gateway:
// Container management
{
type: 'user-containers:new',
name: string,
imageId: string
}
{
type: 'user-containers:delete',
user_container_id: string
}
{
type: 'user-containers:host',
user_container_id: string
}
// Graph operations
{
type: 'core:new-node',
nodeData: { ... }
}
// Chat
{
type: 'chats:new-message',
message: string,
chat_id: string
}State updates from gateway:
Gateway pushes state updates via Yjs synchronization protocol.
| Method | Endpoint | Authentication | Description |
|---|---|---|---|
GET |
/collab/ping |
None | Health check |
POST |
/collab/start |
None (trigger) | Trigger gateway initialization (called by Ganymede) |
GET |
/collab/room-id |
TJwtUser with project_id + project access |
Get room ID for a project |
POST |
/collab/event |
TJwtUser with project_id + project access |
Process collaborative event |
GET |
/collab/vpn-config |
JWT with org:{org_id}:connect-vpn scope |
Get OpenVPN configuration |
POST |
/containers/:containerId/verify-access |
Bearer JWT token | Verify user access to a container (called by Auth Guard Proxy) |
Authentication Details:
GET /collab/room-id: RequiresTJwtUsertoken. Theproject_idcan be provided in JWT payload or as a query parameter (?project_id=...). The user must have project access (member, admin, or org admin/owner). Returns the room_id for the specified project.POST /collab/event: RequiresTJwtUsertoken withproject_idin the JWT payload. The user must have project access (member, admin, or org admin/owner).GET /collab/vpn-config: Requires any JWT token withorg:{org_id}:connect-vpnscope, where{org_id}is the gateway's organization ID. The scope must match exactly (organization-scoped). The gateway resolves{org_id}at runtime and verifies the token contains the matching scope.POST /containers/:containerId/verify-access: Called by Auth Guard Proxy to verify user access to a container. Requires Bearer JWT token in Authorization header. Returns JSON response withallowed(boolean) anduser(user info) fields.
Container applications (JupyterLab, pgAdmin, n8n, etc.) are protected by an Auth Guard Proxy sidecar running inside each container. The auth guard handles OAuth 2.0 Authorization Code flow directly with Ganymede and verifies access via the gateway.
| Method | Endpoint | Authentication | Description |
|---|---|---|---|
POST |
/containers/:containerId/verify-access |
Bearer JWT token | Verify user access to a specific container |
Authentication Details:
POST /containers/:containerId/verify-access: Called by the Auth Guard Proxy after exchanging an OAuth authorization code with Ganymede for a JWT. The gateway checks the user's RBAC permissions (org:owner, org:admin, project:member, project:admin) against the container's project scope.
Note: OAuth client registration for containers is managed centrally via Ganymede's internal API (POST /internal/oauth/clients). See Auth Guard Proxy for full architecture.
Request:
POST /organizations
Content-Type: application/json
{
"name": "my-org"
}Response:
HTTP/1.1 201 Created
Content-Type: application/json
{
"organization_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "my-org",
"owner_user_id": "123e4567-e89b-12d3-a456-426614174000",
"created_at": "2025-01-06T12:00:00.000Z"
}Request:
POST /projects
Content-Type: application/json
{
"organization_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "my-project",
"public": false
}Response:
HTTP/1.1 201 Created
Content-Type: application/json
{
"project_id": "660e8400-e29b-41d4-a716-446655440000",
"organization_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "my-project",
"public": false,
"created_at": "2025-01-06T12:01:00.000Z"
}Request:
GET /projects/660e8400-e29b-41d4-a716-446655440000Response:
HTTP/1.1 200 OK
Content-Type: application/json
{
"project_id": "660e8400-e29b-41d4-a716-446655440000",
"organization_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "my-project",
"public": false,
"created_at": "2025-01-06T12:01:00.000Z"
}Note: Gateway hostname is available separately via organization data. The project response no longer includes gateway information.
Request:
GET /permissions
Authorization: Bearer {jwt_token}Response:
HTTP/1.1 200 OK
Content-Type: application/json
{
"permissions": [
{
"permission": "user-containers:[user-container:*]:create",
"module": "user-containers",
"resourcePath": "user-container:*",
"action": "create",
"description": "Create user containers"
},
{
"permission": "gateway:[permissions:*]:read",
"module": "gateway",
"resourcePath": "permissions:*",
"action": "read",
"description": "Read permissions"
}
]
}Request:
GET /permissions/projects/660e8400-e29b-41d4-a716-446655440000
Authorization: Bearer {jwt_token}Response:
HTTP/1.1 200 OK
Content-Type: application/json
{
"permissions": {
"123e4567-e89b-12d3-a456-426614174000": [
"user-containers:[user-container:*]:create",
"user-containers:[user-container:*]:delete"
],
"223e4567-e89b-12d3-a456-426614174001": [
"user-containers:[user-container:*]:create"
]
}
}Request:
PATCH /permissions/projects/660e8400-e29b-41d4-a716-446655440000/users/123e4567-e89b-12d3-a456-426614174000
Authorization: Bearer {jwt_token}
Content-Type: application/json
{
"permissions": [
"user-containers:[user-container:*]:create",
"user-containers:[user-container:*]:delete"
]
}Response:
HTTP/1.1 200 OK
Content-Type: application/json
{
"success": true
}Standard HTTP error codes:
| Code | Meaning |
|---|---|
400 |
Bad Request - Invalid input |
401 |
Unauthorized - Not authenticated |
403 |
Forbidden - Not authorized |
404 |
Not Found - Resource doesn't exist |
409 |
Conflict - Resource already exists |
500 |
Internal Server Error |
Error Response Format:
{
"error": "Error message describing what went wrong"
}Detailed API schemas are defined in OpenAPI 3.0 format:
- Ganymede:
packages/app-ganymede/src/oas30.json - Gateway:
packages/app-gateway/src/oas30.json
These specs include:
- Complete request/response schemas
- Validation rules
- Parameter descriptions
- Example values
- Architecture Overview - System architecture
- Local Development - Testing APIs locally
- Contributing - Development workflow