@alliumcloud/organization-service
v2.0.23
Published
Official SDK for the Odyssey Organization backend
Maintainers
Readme
@alliumcloud/organization-service
Official TypeScript SDK for the Odyssey Organization backend. Provides a fully typed, modular HTTP client for health checks, organizations, invites, spaces, space templates, space invites, and uploads.
Table of Contents
- Installation
- Quick Start
- Architecture
- Configuration
- Error Handling
- Debug Logging
- API Reference
- Types Reference
- Versioning
Installation
npm install @alliumcloud/organization-service
# or
yarn add @alliumcloud/organization-service
# or
pnpm add @alliumcloud/organization-serviceQuick Start
import { createOrganizationSDK } from "@alliumcloud/organization-service";
const sdk = createOrganizationSDK({
apiKey: "your-api-key",
accessTokenProvider: () => getUserAccessToken(),
});
await sdk.health.getHealth();
const organization = await sdk.organization.create({
name: "New Game Plus",
domain: "new-game-plus",
});
const space = await sdk.spaces.create({
orgId: organization.id,
name: "Main Lobby",
spaceTemplateId: "template-id",
unrealProject: {
unrealProjectName: "MyUnrealProject",
unrealProjectId: "uproject-id",
unrealProjectVersionId: "uproject-version-id",
},
});You can also use the namespace default export:
import OrganizationSDK from "@alliumcloud/organization-service";
const sdk = OrganizationSDK.create({
apiKey: "your-api-key",
accessTokenProvider: () => getUserAccessToken(),
});Architecture
The SDK is structured as a set of focused modules. Each module handles one domain of the backend API, and all modules share a single configured HTTP client created once at initialization.
createOrganizationSDK({ apiKey })
│
├── sdk.health → /health
├── sdk.organization → /organizations
├── sdk.organizationInvites → /organizations-invite
├── sdk.spaces → /space
├── sdk.spaceInvites → /space-invite
└── sdk.upload → /uploadAlso included: sdk.spaceTemplates -> /space-template
Key design decisions:
- The backend base URL is baked into the SDK at build time and is not configured by consumers.
- Your API key is passed once at initialization and injected automatically as the
x-org-sdk-keyheader on every request. - All request failures are normalized into typed
AlliumErrorobjects with stablenameandcodefields. - Debug logging is opt-in and silent by default.
Configuration
import {
createOrganizationSDK,
OrganizationSDKConfig,
} from "@alliumcloud/organization-service";
const sdk = createOrganizationSDK({
apiKey: "your-api-key",
accessTokenProvider: () => getUserAccessToken(),
jwksUri: "https://your-auth-domain/.well-known/jwks.json",
timeout: 60_000,
});| Option | Type | Required | Default | Description |
| ---------------------- | ------------------------------------ | -------- | ------- | ------------------------------------------------------------------ |
| apiKey | string | Yes | - | API key sent as the x-org-sdk-key header on every request. |
| accessTokenProvider | () => string \| Promise<string> | Yes | - | Called on every request to obtain the caller's access token. |
| jwksUri | string | No | - | Sent as the jwks-uri header on every request, when provided. |
| timeout | number | No | 30000 | Request timeout in milliseconds. |
Error Handling
All SDK errors are instances of AlliumError. You can use isAlliumError() to safely narrow the type in catch blocks.
import { isAlliumError } from "@alliumcloud/organization-service";
try {
const organization = await sdk.organization.getById("org-id");
} catch (err) {
if (isAlliumError(err)) {
console.log(err.name);
console.log(err.code);
console.log(err.message);
}
}Error Types
| err.name | err.code | When it is thrown |
| ------------------------ | ------------------ | ---------------------------------------------------------------------- |
| AlliumConfigError | INVALID_CONFIG | API key is missing or the SDK build has no base URL configured. |
| AlliumAuthError | AUTH_ERROR | Server returns 401 or 403. |
| AlliumValidationError | VALIDATION_ERROR | Server returns 400 or 422. |
| AlliumUploadError | UPLOAD_ERROR | Reserved for upload-specific failures; not currently thrown by any SDK code path. |
| AlliumRequestError | REQUEST_ERROR | Network failure, timeout, or any other non-validation request failure. |
| AlliumSDKError | SDK_ERROR | Reserved generic fallback; not currently thrown by any SDK code path. |
Debug Logging
The SDK has a debug logging hook that is gated behind an opt-in flag, silent by default.
In Node.js:
ODYSSEY_DEBUG=true node your-script.jsIn the browser:
window.ODYSSEY_DEBUG = true;Note: in the current published build, the debug logger is a no-op — enabling
ODYSSEY_DEBUGwill not print anything to the console. This is a known gap; do not rely on it for troubleshooting request/response traffic.
API Reference
Health
sdk.health.getHealth()
Check if the backend service is running. The backend responds with no payload on success (throws on failure).
await sdk.health.getHealth();Returns: Promise<void>
sdk.health.getRedisHealth()
Check Redis connection status.
const redis = await sdk.health.getRedisHealth();Returns: Promise<RedisHealthResponse>
Organizations
sdk.organization.create(data)
Create a new organization.
const organization = await sdk.organization.create({
name: "New Game Plus",
domain: "new-game-plus",
});Returns: Promise<Organization>
sdk.organization.getById(id)
Fetch a single organization by ID.
const organization = await sdk.organization.getById("org-id");Returns: Promise<Organization>
sdk.organization.getByUser()
Fetch organizations for the current authenticated user.
const organizations = await sdk.organization.getByUser();Returns: Promise<Organization[]>
sdk.organization.getBySdkKey()
Fetch organizations by x-org-sdk-key.
const result = await sdk.organization.getBySdkKey();Returns: Promise<OrganizationsBySdkKeyResult>
sdk.organization.getRoles()
Fetch the available organization roles.
const roles = await sdk.organization.getRoles();Returns: Promise<OrganizationRole[]>
sdk.organization.getUsers(orgId)
Fetch organization users by organization ID.
const users = await sdk.organization.getUsers("org-id");Returns: Promise<OrganizationUser[]>
sdk.organization.update(id, data)
Update organization fields.
const updated = await sdk.organization.update("org-id", {
name: "Updated Name",
domain: "new-domain",
logoSmallUrl: "https://example.com/logo.png",
});Returns: Promise<Organization>
sdk.organization.updateUser(userId, data)
Update an organization user.
const user = await sdk.organization.updateUser("user-id", {
roleId: "role-id",
avatarReadyPlayerMeImg: "https://example.com/avatar.png",
});Returns: Promise<OrganizationUser>
sdk.organization.delete(id)
Delete an organization.
const deleted = await sdk.organization.delete("org-id");Returns: Promise<Organization>
sdk.organization.deleteUser(userId)
Delete an organization user.
const deleted = await sdk.organization.deleteUser("user-id");Returns: Promise<OrganizationUser>
Organization Invites
sdk.organizationInvites.create(data)
Create an organization invite.
const invite = await sdk.organizationInvites.create({
orgId: "org-id",
email: "[email protected]",
roleId: "role-id",
});Returns: Promise<OrganizationInvite>
sdk.organizationInvites.updateRole(inviteId, data)
Update the roleId for a pending organization invite.
const invite = await sdk.organizationInvites.updateRole("invite-id", {
roleId: "new-role-id",
});Returns: Promise<OrganizationInvite>
sdk.organizationInvites.accept(inviteId)
Accept an organization invite.
const invite = await sdk.organizationInvites.accept("invite-link-id");Returns: Promise<OrganizationInvite>
sdk.organizationInvites.reject(inviteId)
Reject an organization invite.
const invite = await sdk.organizationInvites.reject("invite-link-id");Returns: Promise<OrganizationInvite>
sdk.organizationInvites.delete(inviteId)
Delete an organization invite.
const deleted = await sdk.organizationInvites.delete("invite-id");Returns: Promise<OrganizationInvite>
sdk.organizationInvites.getPendingUsers(orgId)
Fetch pending invites for an organization.
const invites = await sdk.organizationInvites.getPendingUsers("org-id");Returns: Promise<OrganizationInvite[]>
sdk.organizationInvites.getPendingUserByInviteId(inviteId)
Fetch a pending organization invite by invite link ID.
const invite =
await sdk.organizationInvites.getPendingUserByInviteId("invite-link-id");Returns: Promise<OrganizationInvite | null>
Spaces
sdk.spaces.create(data)
Create a space.
const space = await sdk.spaces.create({
orgId: "org-id",
name: "Main Lobby",
spaceTemplateId: "template-id",
unrealProject: {
unrealProjectName: "MyUnrealProject",
unrealProjectId: "uproject-id",
unrealProjectVersionId: "uproject-version-id",
},
});Returns: Promise<Space>
Notes: id, spaceTemplateId, and unrealProject are all optional (id lets the backend generate one when omitted).
sdk.spaces.getSpaceAll(orgId)
Fetch all spaces for an organization.
const spaces = await sdk.spaces.getSpaceAll("org-id");Returns: Promise<Space[]>
sdk.spaces.getById(spaceId)
Fetch a space by ID.
const space = await sdk.spaces.getById("space-id");Returns: Promise<Space | null>
sdk.spaces.getItems(spaceId)
Fetch the items placed in a space.
const items = await sdk.spaces.getItems("space-id");Returns: Promise<SpaceItem[]>
sdk.spaces.getRoomsByOrgId(orgId)
Fetch rooms by organization ID.
const rooms = await sdk.spaces.getRoomsByOrgId("org-id");Returns: Promise<RoomWithSpaceSummary[]>
sdk.spaces.getByProject(projectId)
Fetch spaces by project ID.
const spaces = await sdk.spaces.getByProject("project-id");Returns: Promise<Space[]>
sdk.spaces.getRoles()
Fetch the available space roles.
const roles = await sdk.spaces.getRoles();Returns: Promise<SpaceRole[]>
sdk.spaces.getUsers(spaceId)
Fetch users for a space.
const users = await sdk.spaces.getUsers("space-id");Returns: Promise<SpaceUser[]>
sdk.spaces.update(spaceId, data)
Update a space.
const updated = await sdk.spaces.update("space-id", {
name: "Updated Space",
description: "Updated description",
thumb: "https://example.com/thumb.png",
});Returns: Promise<Space>
sdk.spaces.updateSetting(spaceId, data)
Update a space setting record.
const setting = await sdk.spaces.updateSetting("space-id", {
isPublic: true,
allowAnonymousUsers: false,
odysseyMobileControls: "ON",
avatarControlSystem: "EVENT_MODE",
});Returns: Promise<SpaceSetting>
sdk.spaces.updateUser(userId, data)
Update a space user.
const user = await sdk.spaces.updateUser("space-user-id", {
roleId: "role-id",
avatarUrl: "https://example.com/avatar.png",
});Returns: Promise<SpaceUser>
sdk.spaces.delete(spaceId)
Delete a space.
const deleted = await sdk.spaces.delete("space-id");Returns: Promise<Space>
sdk.spaces.deleteUser(userId)
Delete a space user.
const deleted = await sdk.spaces.deleteUser("space-user-id");Returns: Promise<SpaceUser>
Space Templates
sdk.spaceTemplates.getByOrganization(orgId)
Fetch space templates by organization ID.
const templates = await sdk.spaceTemplates.getByOrganization("org-id");Returns: Promise<SpaceTemplate[]>
sdk.spaceTemplates.getByUnrealProject(unrealProjectId)
Fetch space templates by Unreal project ID.
const templates = await sdk.spaceTemplates.getByUnrealProject("uproject-id");Returns: Promise<SpaceTemplate[]>
Space Invites
sdk.spaceInvites.create(data)
Create a space invite.
const invite = await sdk.spaceInvites.create({
spaceId: "space-id",
email: "[email protected]",
roleId: "role-id",
});Returns: Promise<SpaceInvite>
sdk.spaceInvites.updateRole(inviteId, data)
Update the roleId for a pending space invite.
const invite = await sdk.spaceInvites.updateRole("invite-id", {
roleId: "new-role-id",
});Returns: Promise<SpaceInvite>
sdk.spaceInvites.accept(inviteId)
Accept a space invite.
const invite = await sdk.spaceInvites.accept("invite-link-id");Returns: Promise<SpaceInvite>
sdk.spaceInvites.reject(inviteId)
Reject a space invite.
const invite = await sdk.spaceInvites.reject("invite-link-id");Returns: Promise<SpaceInvite>
sdk.spaceInvites.delete(inviteId)
Delete a space invite.
const deleted = await sdk.spaceInvites.delete("invite-id");Returns: Promise<SpaceInvite>
sdk.spaceInvites.getPendingUsers(spaceId)
Fetch pending invites for a space.
const invites = await sdk.spaceInvites.getPendingUsers("space-id");Returns: Promise<SpaceInvite[]>
sdk.spaceInvites.getPendingUserByInviteId(inviteId)
Fetch a pending space invite by invite link ID.
const invite =
await sdk.spaceInvites.getPendingUserByInviteId("invite-link-id");Returns: Promise<SpaceInvite | null>
Upload
sdk.upload.getPresignedUrl(fileType)
Create a presigned URL to upload a file to storage.
const presigned = await sdk.upload.getPresignedUrl("image/png");
// Upload from Node.js (example)
await fetch(presigned.url, {
method: presigned.method,
headers: presigned.headers,
body: yourFileBuffer,
});Returns: Promise<UploadPresignedUrl>
Types Reference
Most types are exported directly from the package root:
import type { ... } from "@alliumcloud/organization-service";Note:
SpaceItem,UnrealProject,Room,RoomSpaceSummary,RoomWithSpaceSummary, andUpdateSpaceInviteRoleInputare documented below for reference but are not currently re-exported from the package root — importing them by name will fail until they're added to the SDK's export list.
Config
type OrganizationSDKConfig = {
apiKey: string;
jwksUri?: string;
accessTokenProvider: () => string | Promise<string>;
timeout?: number;
};Common
type PaginationQuery = {
page?: number;
limit?: number;
};
type PaginationMeta = {
total: number;
page: number;
limit: number;
totalPages: number;
};
type InviteStatus = "PENDING" | "ACCEPTED" | "REJECTED" | "EXPIRES";
type InviteType = "EMAIL" | "LINK";
type OdysseyMobileControls = "ON" | "OFF" | "JOYSTICK_ONLY";
type AvatarType = "STANDARD" | "AEC";
type AvatarControlSystem = "EVENT_MODE" | "GAME_MODE" | "FLIGHT_MODE";
type RoomState =
| "DEPROVISIONED"
| "PROVISIONING"
| "POD_READY"
| "DEPROVISIONING"
| "FAILED_PROVISIONING"
| "FAILED_DEPROVISIONING"
| "TIMED_OUT_PROVISIONING";Organization Types
type Organization = {
id: string;
name: string;
domain: string;
logoSmallUrl: string | null;
sdkKeyId?: string | null;
whitelabel: boolean | null;
allowSharedInviteLinks: boolean | null;
alliumOrganizationId: string | null;
projectId: string | null;
createdAt: string;
updatedAt: string;
};
type OrganizationRole = {
id: string;
name: string;
key: string;
resourceType: string;
createdAt: string;
updatedAt: string;
};
type OrganizationUser = {
id: string;
orgId: string;
projectId?: string | null;
userId: string;
email: string;
name?: string | null;
roleId: string;
avatarUrl?: string | null;
avatarReadyPlayerMeImg: string | null;
createdAt: string;
updatedAt: string;
role?: OrganizationRole | null;
organization?: Organization;
};
type OrganizationConfiguration = {
id: string;
orgId: string;
unrealImageId: string | null;
unrealImageRepo: string | null;
workloadClusterProvider: string | null;
newShardParticipantsThreshold: number | null;
defaultRegion: string | null;
createdAt: string;
updatedAt: string;
organization?: Organization;
};
type CreateOrganizationInput = {
domain?: string | null;
name: string;
};
type UpdateOrganizationInput = {
name?: string | null;
domain?: string | null;
logoSmallUrl?: string | null;
};
type UpdateOrganizationUserInput = {
roleId?: string;
avatarReadyPlayerMeImg?: string;
};
type OrganizationInvite = {
id: string;
orgId: string;
projectId: string | null;
email: string;
roleId: string;
inviteLinkId: string;
avatarReadyPlayerMeImg: string | null;
actionAt: string | null;
status: InviteStatus;
type: InviteType;
createdAt: string;
updatedAt: string;
organization?: Organization;
};
type CreateOrganizationInviteInput = {
redirectUrl?: string | null;
orgId: string;
email: string;
roleId: string;
};
type UpdateOrganizationInviteRoleInput = {
roleId: string;
};
type OrganizationsBySdkKeyResult = {
organization: Organization[];
};Space Types
type UnrealProject = {
unrealProjectName: string;
unrealProjectId: string;
unrealProjectVersionId: string;
};
type GetSpaces = {
unrealProjectName: string;
unrealProjectId: string;
space: Space[];
};
type SpaceConfiguration = {
id: string;
spaceId: string;
orgId: string;
type: SpaceConfigurationType;
workloadClusterProvider: string | null;
workloadClusterProviders: unknown;
workloadRegion: string | null;
unrealImageId: string | null;
unrealImageRepo: string | null;
unrealCpuM: number | null;
unrealMemoryMb: number | null;
unrealBaseCliArgs: string | null;
unrealOverrideCliArgs: string | null;
unrealMountContent: boolean | null;
unrealMountImageId: string | null;
unrealMountImageRepo: string | null;
unrealMountContainsClientAndServer: boolean | null;
unrealProjectName: string | null;
firebaseBridgeImageId: string | null;
firebaseBridgeImageRepo: string | null;
firebaseApiKey: string | null;
newShardParticipantsThreshold: number | null;
createdAt: string;
updatedAt: string;
};
type SpaceItem = {
id: string;
spaceId: string;
name: string;
type: string;
thumb: string | null;
isLocked: boolean | null;
userId: string | null;
data: unknown;
position: unknown;
rotation: unknown;
offsetUpRotation: number;
createdAt: string;
updatedAt: string;
space?: Space;
};
type Room = {
id: string;
spaceId: string;
orgId: string;
name: string;
region: string;
state: RoomState;
avatarControlSystem: AvatarControlSystem;
currentAdminCount: number;
currentParticipantCount: number;
participantCount: number;
provisioningFailures: number;
deprovisioningFailures: number;
enableSharding: boolean;
isLiveStreamActive: boolean;
isPublic: boolean;
shardOf: string | null;
serverAddress: string | null;
staticServer: boolean | null;
levelId: string | null;
graphicsBenchmark: number | null;
lastPingFromServer: string | null;
createdAt: string;
updatedAt: string;
space?: Space;
organization?: Organization;
};
type RoomSpaceSummary = {
id: string;
name: string;
};
type RoomWithSpaceSummary = Omit<Room, "space"> & {
space?: RoomSpaceSummary;
};
type Space = {
id: string;
orgId: string;
projectId: string | null;
spaceTemplateId: string | null;
name: string;
description: string | null;
thumb: string | null;
unrealProjectId: string | null;
unrealProjectName: string | null;
unrealProjectVersionId: string | null;
ueId?: string | null;
alliumOrganizationId: string | null;
sdkKeyId: string | null;
originalSpaceId?: string | null;
currentParticipantSum: number;
spaceItemSum: number;
createdAt: string;
updatedAt: string;
organization?: Organization;
spaceSetting?: SpaceSetting | null;
spaceConfigurations?: SpaceConfiguration[];
spaceTemplate?: SpaceTemplate | null;
spaceUsers?: SpaceUser[];
rooms?: Room[];
spaceInvites?: SpaceInvite[];
spaceItems?: SpaceItem[];
spaceHistories?: unknown[];
spaceStreams?: unknown[];
participantUsages?: unknown[];
completedParticipants?: unknown[];
recentSpaceUsers?: unknown[];
};
type SpaceSetting = {
id: string;
spaceId: string;
isPublic: boolean;
afkTimer: number;
maxSessionLength: number;
allowAnonymousUsers: boolean;
allowEmbed: boolean;
allowConfigurationToolbarForAllUsers: boolean;
disableChat: boolean;
disableComms: boolean;
enableSharding: boolean;
isLiveStreamActive: boolean;
showHelpMenu: boolean;
showLoadingBackground: boolean;
showLoadingBackgroundBlur: boolean;
showOdysseyEditorMenu: boolean;
showSpaceInformation: boolean;
nonViewerHuddle: boolean;
maximumResolution?: unknown;
odysseyMobileControls: OdysseyMobileControls;
avatarType: AvatarType;
avatarControlSystem: AvatarControlSystem;
createdAt: string;
updatedAt: string;
space?: Space;
};
type SpaceUser = {
id: string;
spaceId: string;
email: string;
name: string;
userId: string;
roleId: string;
isPending: boolean;
avatarUrl: string | null;
createdAt: string;
updatedAt: string;
role?: SpaceRole | null;
space?: Space;
};
type SpaceRole = {
id: string;
name: string;
key: string;
resourceType: string;
createdAt: string;
updatedAt: string;
};
type SpaceInvite = {
id: string;
spaceId: string;
email: string;
roleId: string;
inviteLinkId: string;
actionAt: string | null;
status: InviteStatus;
type: InviteType;
createdAt: string;
updatedAt: string;
space?: Space | { orgId: string; projectId: string } | null;
};
type CreateSpaceInput = {
id?: string | null;
name: string;
orgId: string;
spaceTemplateId?: string;
unrealProject?: UnrealProject;
};
type UpdateSpaceInput = {
name?: string;
description?: string | null;
thumb?: string | null;
};
type UpdateSpaceSettingInput = {
isPublic?: boolean;
afkTimer?: number;
maxSessionLength?: number;
allowAnonymousUsers?: boolean;
allowEmbed?: boolean;
allowConfigurationToolbarForAllUsers?: boolean;
disableChat?: boolean;
disableComms?: boolean;
enableSharding?: boolean;
isLiveStreamActive?: boolean;
showHelpMenu?: boolean;
showLoadingBackground?: boolean;
showLoadingBackgroundBlur?: boolean;
showOdysseyEditorMenu?: boolean;
showSpaceInformation?: boolean;
nonViewerHuddle?: boolean;
maximumResolution?: unknown;
odysseyMobileControls?: OdysseyMobileControls;
avatarType?: AvatarType;
avatarControlSystem?: AvatarControlSystem;
};
type UpdateSpaceUserInput = {
roleId?: string;
avatarUrl?: string;
};
type CreateSpaceInviteInput = {
inviteLinkId: string | null;
redirectUrl?: string;
spaceId: string;
email: string;
roleId: string;
};
type UpdateSpaceInviteRoleInput = {
roleId: string;
};Space Template Types
type SpaceTemplateType = "ODYSSEY" | "BRIDGE";
type SpaceConfigurationType = "ODYSSEY_SERVER" | "ODYSSEY_CLIENT_POD";
type SpaceTemplate = {
id: string;
orgId: string;
name: string;
description: string | null;
thumb: string | null;
type: SpaceTemplateType;
ueId: string | null;
isPublic: boolean;
hasSpaceItems: boolean | null;
demoUrl: string | null;
unrealProjectId: string | null;
unrealProjectVersionId: string | null;
createdAt: string;
updatedAt: string;
};
type SpaceTemplateItem = {
id: string;
spaceTemplateId: string;
itemTemplateId: string;
name: string;
type: string;
denormalizeOnUpdate: boolean;
data: unknown;
position: unknown;
rotation: unknown;
offsetUpRotation: number;
createdAt: string;
updatedAt: string;
};Upload Types
type UploadPresignedUrl = {
url: string;
method: "PUT";
headers: {
"Content-Type": string;
};
};Health Types
type RedisHealthResponse =
| { connected: true; message: string; ping: "success" | "failed" }
| { connected: false; message: string; error?: string };Error Types
interface AlliumError extends Error {
readonly code: string;
}
function isAlliumError(err: unknown): err is AlliumError;Versioning
This SDK follows semantic versioning:
| Bump | When |
| ----------------- | ---------------------------------------------------------- |
| patch - x.x.1 | Bug fixes with no public API changes. |
| minor - x.1.0 | New methods or fields added in a backwards-compatible way. |
| major - 2.0.0 | Breaking changes to existing method signatures or types. |
License
MIT
