A TypeScript client library for interacting with the OpenAir SuiteProjects Pro (SPP) XML API. This package provides a core SPPClient for making direct API calls and pre-built, type-safe BuisinessObject Services for all SuiteProjects Pro objects.
SPPClient for flexible PI communication via OpenAir/SPP XML API.onRefresh (to persist new tokens) and onError (for critical client errors).SPPAuthError, SPPBusinessError, etc.).SPPStatus codes and SPPStatusInfo for easy error identification and handling.To use @theredspace/spp-api-client, configure your package manager to fetch packages from GitHub Packages under the @theredspace scope.
1. Configure .npmrc
Option A: Edit .npmrc file directly (Recommended)
@theredspace:registry=https://npm.pkg.github.com
For private packages or CI/CD, add:
//npm.pkg.github.com/:_authToken=YOUR_GITHUB_TOKEN
Replace YOUR_GITHUB_TOKEN with a GitHub Personal Access Token (PAT) with read:packages scope.
Option B: npm login (for local development)
npm login --registry=https://npm.pkg.github.com --scope=@theredspace
2. Install the Package
npm install @theredspace/spp-client@latest
# or
yarn add @theredspace/spp-client@latest
Before making API calls, you need OAuth tokens. Follow these steps:
import { SPPClient } from "@theredspace/spp-api-client";
const authClient = new SPPClient({
sppUrl: process.env.SPP_URL,
clientId: process.env.SPP_CLIENT_ID,
clientSecret: process.env.SPP_CLIENT_SECRET,
callbackUrl: process.env.SPP_CALLBACK_URL,
});
const authorizationUrl = authClient.getAuthUrl();
console.log("Visit this URL to authorize:", authorizationUrl);
code query parameter at your callback URL. Exchange it for tokens:curl -X POST "${SPP_URL}/login/oauth2/v1/token" \
-u "${CLIENT_ID}:${CLIENT_SECRET}" \
-d "grant_type=authorization_code&code=AUTH_CODE&redirect_uri=${CALLBACK_URL}"
Response:{
"access_token": "<ACCESS_TOKEN>",
"refresh_token": "<REFRESH_TOKEN>"
}
const client = new SPPClient({
sppUrl: process.env.SPP_URL,
clientId: process.env.SPP_CLIENT_ID,
clientSecret: process.env.SPP_CLIENT_SECRET,
callbackUrl: process.env.SPP_CALLBACK_URL,
accessToken: "<ACCESS_TOKEN>",
refreshToken: "<REFRESH_TOKEN>",
onRefresh: async ({ access_token, refresh_token }) => {
// persist new tokens
},
});
export SPP_URL="https://your.openair.com/suiteprojects/etc/etc/"
export SPP_CLIENT_ID="<your_client_id>"
export SPP_CLIENT_SECRET="<your_client_secret>"
export SPP_CALLBACK_URL="<your_callback_url>"
import { SPPClient } from "@theredspace/spp-api-client";
const client = new SPPClient({
sppUrl: process.env.SPP_URL,
clientId: process.env.SPP_CLIENT_ID,
clientSecret: process.env.SPP_CLIENT_SECRET,
callbackUrl: process.env.SPP_CALLBACK_URL,
onRefresh: async ({ access_token, refresh_token }) => {
// persist tokens securely
},
});
The SPP Client includes a built-in logger that prefixes all output with [RSSPPC] to easily identify library logs. By default, logging is disabled.
Simple enable - Enable logging with default 'log' level:
const client = new SPPClient({
// ... other options
logging: true // Shows log, warn, and error messages
});
Advanced configuration - Full control over log levels:
const client = new SPPClient({
// ... other options
logging: {
enabled: true,
level: 'debug' // Options: 'debug' | 'info' | 'log' | 'warn' | 'error' | 'none'
}
});
Use static methods to control logging globally after client initialization:
// Enable/disable logging
SPPClient.enableLogging();
SPPClient.disableLogging();
// Set log level
SPPClient.setLogLevel('error'); // Only show errors
SPPClient.setLogLevel('warn'); // Show warnings and errors
SPPClient.setLogLevel('debug'); // Show everything
// Or use full configuration
SPPClient.configureLogging({
enabled: true,
level: 'info'
});
logging: true)All log messages are prefixed with [RSSPPC] and include a namespace (e.g., [RSSPPC] [SPPClient] Making API call...).
List Method Examples
// Fetch all records for an object
const users = await client.list("User");
// Fetch one record with a filter
const customers = await client.list("Customer", { country: "US" });
// Fetch many records with an array of ids using batchList
const items = await client.batchList(
"Schedulerequest_item",
itemIds.map((id) => ({ id: String(id) }))
);
Add Method Examples
// Add a single object
const invoice = await client.add("Invoice", {
customerid: 1,
total: 100,
});
// Add object with Date property
const request = await client.add("Schedulerequest", {
startdate: "2024-06-01",
enddate: "2024-06-05",
userid: 42,
externalid: "external-123",
});
// Add multiple new objects at once
const newProjects = [
{
name: "Website Redesign",
customerid: 1,
externalid: "proj-001",
},
{
name: "Mobile App Launch",
customerid: 2,
externalid: "proj-002",
},
];
const createdProjects = await client.add("Project", newProjects);
Update Method Examples
// Update one Object by id, passing in change object.
const updatedUser = await client.update("User", 16, {
nickname: "janedoe@example.com",
});
// Update many Objects by passing in an array of change objects.
const updates = [
{ id: 101, changes: { status: "Active" } },
{ id: 102, changes: { status: "Completed" } },
{ id: 103, changes: { status: "On Hold" } },
];
const updatedProjects = await client.update("Project", updates);
Delete Method Examples:
// Delete one Object by id. Returns an array of Results
const deletedInvoice = await client.delete("Invoice", 123);
// Delete many Objects by an array of ids
const deletedInvoices = await client.delete("Invoice", [123, 456]);
Important:
The SuiteProjects API enforces rate limits based on your organizations' account. If you exceed this limit, your requests may be throttled or rejected.
add, update, or delete.If you need to process more records, consider spreading your requests over time or paginating your requests.
All methods throw typed errors that extend JavaScript's Error class. You can catch and handle these using try/catch blocks. The main error types are:
SPPAuthError – Authentication or token issuesSPPRequestError – Network or HTTP-level failures (e.g., no response)SPPResponseError – Malformed or unexpected API responsesSPPBusinessError – API-level business logic errors (non-zero SPP status codes)Each error includes a .detail property with a code and message for debugging.
import {
SPPAuthError,
SPPBusinessError,
SPPStatus,
SPPStatusInfo,
} from "@thespace/spp-api-client";
try {
await client.add("User", { name: "Test", externalid: "test-001" });
} catch (err) {
if (err instanceof SPPAuthError) {
console.error("Authentication error:", err.detail.message);
} else if (err instanceof SPPBusinessError) {
const code = err.detail.code;
const info = SPPStatusInfo[code as SPPStatus];
console.error(
`Business error: ${info?.name || code} - ${
info?.description || err.detail.message
}`
);
} else {
console.error("Unexpected error:", err);
}
}
code (see SPPStatus enum).SPPStatusInfo.Example:
const code = 202; // e.g., from err.detail.code
const info = SPPStatusInfo[code];
console.log(info.name); // "UserDuplicateNickname"
console.log(info.description); // "There is already a user with the same User ID (nickname)"
For a full list of status codes and their meanings, see src/utils/errorCodes.ts.
Tip:
Always wrap API calls in try/catch and use the error classes and status info to provide clear feedback to users and for debugging.