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 BusinessObject Services for all SuiteProjects Pro
objects.
SPPClient
for flexible XML API communication.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
//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 2.0 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);
After the user grants access, your app receives a
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": "",
"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: "",
refreshToken: "",
onRefresh: async ({ access_token, refresh_token }) => {
// persist new tokens
},
});
1. Configure environment (either via constructor or environment variables):
export SPP_URL="https://your.openair.com/suiteprojects/etc/etc/"
export SPP_CLIENT_ID=""
export SPP_CLIENT_SECRET=""
export SPP_CALLBACK_URL=""
2. Initialize client:
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
},
});
// 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 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 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 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 a limit of 1,000 requests at a
time. If you exceed this limit, your requests may be throttled or
rejected.
Best Practices:
add
, update
, or delete
.If you need to process more than 1,000 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 failuresSPPResponseError
– 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 "@theredspace/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);
}
}
All business/API errors include a numeric or string code
(see the SPPStatus
enum). You can look up
human-friendly names and descriptions using
SPPStatusInfo
.
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.
© 2025 The REDspace · Apache-2.0