@theredspace/spp-client
    Preparing search index...

    @theredspace/spp-client

    @theredspace/spp-client API Module

    License: Apache-2.0

    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.

    • Modern ECMAScript Module (ESM) and TypeScript-first design.
    • Core SPPClient for flexible PI communication via OpenAir/SPP XML API.
    • Type-safe services for all SPP objects (e.g., Invoices, Projects, Users, etc.).
    • Handles OAuth 2.0 authentication, including automatic token refresh.
    • Callbacks for onRefresh (to persist new tokens) and onError (for critical client errors).
    • Automatic parsing of XML responses to JavaScript objects.
    • Comprehensive error handling with custom, typed errors (SPPAuthError, SPPBusinessError, etc.).
    • Extensive list of SPPStatus codes and SPPStatusInfo for easy error identification and handling.

    Documentation

    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:

    1. Get the authorization URL and redirect the user
      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);
    2. Exchange the authorization code for tokens 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": "<ACCESS_TOKEN>",
      "refresh_token": "<REFRESH_TOKEN>"
      }
    3. Initialize client with tokens
      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
      },
      });
    1. Configure environment (either via constructor or env vars):
      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>"
    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
      },
      });

    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'
    });
    • debug - All logs including debug information (most verbose)
    • info - Informational messages and above
    • log - Standard logs, warnings, and errors (default when logging: true)
    • warn - Warnings and errors only
    • error - Errors only (least verbose)
    • none - No logs at all

    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.

    • Best Practices:
      • Batch your requests by passing arrays to add, update, or delete.
      • Use pagination for large data sets.
      • Implement retry logic with exponential backoff for 429 (Too Many Requests) errors.
      • Monitor your request volume and avoid sending bursts of requests.

    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 issues
    • SPPRequestError – Network or HTTP-level failures (e.g., no response)
    • SPPResponseError – Malformed or unexpected API responses
    • SPPBusinessError – 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);
    }
    }
    • All business/API errors include a numeric or string code (see SPPStatus enum).
    • You can look up human-friendly names and descriptions using 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.