@newgameplusinc/odyssey-organization-sdk
v1.0.7
Published
Official SDK for the Odyssey Organization backend
Readme
@newgameplusinc/odyssey-organization-sdk
Official TypeScript SDK for the Odyssey Organization backend. Provides a fully typed, modular HTTP client for health checks, organizations, invites, projects, spaces, space invites, and uploads.
Table of Contents
- Installation
- Quick Start
- Architecture
- Configuration
- Error Handling
- Debug Logging
- API Reference
- Types Reference
- Versioning
Installation
npm install @newgameplusinc/odyssey-organization-sdk
# or
yarn add @newgameplusinc/odyssey-organization-sdk
# or
pnpm add @newgameplusinc/odyssey-organization-sdkQuick Start
import { createOrganizationSDK } from "@newgameplusinc/odyssey-organization-sdk";
const sdk = createOrganizationSDK({ apiKey: "your-api-key" });
const health = await sdk.health.getHealth();
console.log(health.status);
const organization = await sdk.organization.create({
name: "New Game Plus",
});
const projects = await sdk.projects.getByOrg(organization.id, {
page: 1,
limit: 10,
});
const space = await sdk.spaces.create({
orgId: organization.id,
name: "Main Lobby",
spaceTemplateId: "template-id",
unrealProject: {
unrealProjectId: "uproject-id",
unrealProjectVersionId: "uproject-version-id",
},
});You can also use the namespace default export:
import OrganizationSDK from "@newgameplusinc/odyssey-organization-sdk";
const sdk = OrganizationSDK.create({ apiKey: "your-api-key" });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.projects → /projects
├── sdk.spaces → /spaces
├── sdk.spaceInvites → /space-invite
└── sdk.upload → /uploadKey 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
OdysseyErrorobjects with stablenameandcodefields. - Debug logging is opt-in and silent by default.
Configuration
import {
createOrganizationSDK,
OrganizationSDKConfig,
} from "@newgameplusinc/odyssey-organization-sdk";
const sdk = createOrganizationSDK({
apiKey: "your-api-key",
timeout: 60_000,
});| Option | Type | Required | Default | Description |
| --------- | -------- | -------- | ------- | -------------------------------------------------------- |
| apiKey | string | Yes | - | API key sent as the x-org-sdk-key header on every request. |
| timeout | number | No | 30000 | Request timeout in milliseconds. |
Error Handling
All SDK errors are instances of OdysseyError. You can use isOdysseyError() to safely narrow the type in catch blocks.
import { isOdysseyError } from "@newgameplusinc/odyssey-organization-sdk";
try {
const organization = await sdk.organization.getById("org-id");
} catch (err) {
if (isOdysseyError(err)) {
console.log(err.name);
console.log(err.code);
console.log(err.message);
}
}Error Types
| err.name | err.code | When it is thrown |
| ------------------------ | ------------------ | ---------------------------------------------------------------------- |
| OdysseyConfigError | INVALID_CONFIG | API key is missing or the SDK build has no base URL configured. |
| OdysseyAuthError | AUTH_ERROR | Server returns 401 or 403. |
| OdysseyValidationError | VALIDATION_ERROR | Server returns 400 or 422. |
| OdysseyUploadError | UPLOAD_ERROR | Upload-specific failure category exported by the SDK error helpers. |
| OdysseyRequestError | REQUEST_ERROR | Network failure, timeout, or any other non-validation request failure. |
| OdysseySDKError | SDK_ERROR | Generic fallback error. |
Debug Logging
The SDK ships with a built-in debug logger that is silent by default. Enable it to see outgoing requests, response statuses, and module-level operations.
In Node.js:
ODYSSEY_DEBUG=true node your-script.jsIn the browser:
window.ODYSSEY_DEBUG = true;Example log output:
[14:23:01.452][SDK:HTTP] → POST /organizations
[14:23:01.891][SDK:HTTP] ← 201 /organizations
[14:23:01.892][SDK:Organization] create { id: "..." }API Reference
Health
sdk.health.getHealth()
Check if the backend service is running.
const health = await sdk.health.getHealth();Returns: Promise<HealthResponse>
sdk.health.getRedisHealth()
Check Redis health and latency.
const redis = await sdk.health.getRedisHealth();Returns: Promise<RedisHealthResponse>
sdk.health.testRedis()
Run a Redis test operation.
await sdk.health.testRedis();Returns: Promise<void>
Organizations
sdk.organization.create(data)
Create a new organization.
const organization = await sdk.organization.create({
name: "New Game Plus",
});Returns: Promise<Organization>
sdk.organization.getAll(query?)
Fetch paginated organizations.
const result = await sdk.organization.getAll({
page: 1,
limit: 10,
});Returns: Promise<OrganizationListResult>
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.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.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>
Projects
sdk.projects.create(data)
Create a project.
const project = await sdk.projects.create({
orgId: "org-id",
name: "Project Alpha",
description: "Primary experience",
});Returns: Promise<Project>
sdk.projects.getByOrg(orgId, query?)
Fetch paginated projects by organization ID.
const projects = await sdk.projects.getByOrg("org-id", {
page: 1,
limit: 10,
});Returns: Promise<ProjectListResult>
sdk.projects.getById(projectId)
Fetch a project by ID.
const project = await sdk.projects.getById("project-id");Returns: Promise<Project>
sdk.projects.update(projectId, data)
Update a project.
const project = await sdk.projects.update("project-id", {
name: "Updated Project",
description: "Updated description",
isActive: true,
});Returns: Promise<Project>
sdk.projects.delete(projectId)
Delete a project.
const deleted = await sdk.projects.delete("project-id");Returns: Promise<Project>
Spaces
sdk.spaces.create(data)
Create a space.
const space = await sdk.spaces.create({
orgId: "org-id",
name: "Main Lobby",
spaceTemplateId: "template-id",
unrealProject: {
unrealProjectId: "uproject-id",
unrealProjectVersionId: "uproject-version-id",
},
});Returns: Promise<Space>
sdk.spaces.getSpaceAll()
Fetch all spaces for the current SDK key (organization/project inferred from x-org-sdk-key).
const spaces = await sdk.spaces.getSpaceAll();Returns: Promise<GetSpaces[]>
sdk.spaces.getByProject(projectId)
Fetch spaces by project ID.
const spaces = await sdk.spaces.getByProject("project-id");Returns: Promise<Space[]>
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 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.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.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
All types are exported directly from the package root:
import type { ... } from "@newgameplusinc/odyssey-organization-sdk";Config
type OrganizationSDKConfig = {
apiKey: 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";Organization Types
type Organization = {
id: string;
name: string;
domain: string;
logoSmallUrl: string | null;
sdkKeyId?: string | null;
createdAt: string;
updatedAt: string;
};
type OrganizationUser = {
id: string;
orgId: string;
email: string;
roleId: string;
avatarReadyPlayerMeImg: string | null;
createdAt: string;
updatedAt: string;
organization?: Organization;
};
type OrganizationConfiguration = {
id: string;
orgId: string;
unrealImageId: string;
unrealImageRepo: string;
workloadClusterProvider: string;
createdAt: string;
updatedAt: string;
organization?: Organization;
};
type OrganizationByUserResult = {
organization: Organization;
};
type CreateOrganizationInput = {
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;
email: string;
roleId: string;
inviteLinkId: string;
avatarReadyPlayerMeImg: string | null;
actionAt: string | null;
status: InviteStatus;
type: InviteType;
createdAt: string;
updatedAt: string;
organization?: Organization;
};
type CreateOrganizationInviteInput = {
orgId: string;
email: string;
roleId: string;
};
type UpdateOrganizationInviteRoleInput = {
roleId: string;
};
type OrganizationListResult = {
data: Organization[];
meta: PaginationMeta;
};
type OrganizationsBySdkKeyResult = {
organization: Organization[];
};Project Types
type Project = {
id: string;
orgId: string;
name: string;
description: string | null;
isActive: boolean;
createdAt: string;
updatedAt: string;
organization?: Organization;
};
type CreateProjectInput = {
orgId: string;
name: string;
description?: string | null;
};
type UpdateProjectInput = {
name?: string;
description?: string | null;
isActive?: boolean;
};
type ProjectListResult = {
data: Project[];
meta: PaginationMeta;
};Space Types
type UnrealProject = {
unrealProjectId: string;
unrealProjectVersionId: string;
};
type Space = {
id: string;
orgId: string;
projectId: string;
name: string;
description: string | null;
unrealProject: UnrealProject | Record<string, unknown>;
currentParticipantSum: number;
spaceTemplateId: string;
thumb: string | null;
spaceItemSum: number;
createdAt: string;
updatedAt: string;
organization?: Organization;
spaceSetting?: SpaceSetting | null;
};
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;
notViewerBuddle: boolean;
maximumResolution?: unknown;
odysseyMobileControls: OdysseyMobileControls;
avatarType: AvatarType;
avatarControlSystem: AvatarControlSystem;
createdAt: string;
updatedAt: string;
space?: Space;
};
type SpaceUser = {
id: string;
spaceId: string;
email: string;
roleId: string;
isPending: boolean;
avatarUrl: string | null;
createdAt: string;
updatedAt: string;
space?: Space;
};
type SpaceInvite = {
id: string;
spaceId: string;
email: string;
roleId: string;
inviteLinkId: string;
avatarReadyPlayerMeImg: string | null;
actionAt: string | null;
status: InviteStatus;
type: InviteType;
createdAt: string;
updatedAt: string;
space?: Space;
};
type CreateSpaceInput = {
orgId: string;
name: string;
spaceTemplateId: string;
unrealProject: {
unrealProjectId: string;
unrealProjectVersionId: string;
};
};
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;
notViewerBuddle?: boolean;
maximumResolution?: unknown;
odysseyMobileControls?: OdysseyMobileControls;
avatarType?: AvatarType;
avatarControlSystem?: AvatarControlSystem;
};
type UpdateSpaceUserInput = {
roleId?: string;
avatarUrl?: string;
};
type CreateSpaceInviteInput = {
spaceId: string;
email: string;
roleId: string;
};Upload Types
type UploadPresignedUrl = {
url: string;
method: "PUT";
headers: {
"Content-Type": string;
};
};Health Types
type HealthResponse = {
status: string;
timestamp: string;
};
type RedisHealthResponse = {
status: string;
latencyMs?: number;
};Error Types
interface OdysseyError extends Error {
readonly code: string;
}
function isOdysseyError(err: unknown): err is OdysseyError;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
