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

    @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 BusinessObject Services for all SuiteProjects Pro objects.

    • Modern ECMAScript Module (ESM) and TypeScript-first design.
    • Core SPPClient for flexible XML API communication.
    • Type-safe services for all SPP objects (e.g., Invoices, Projects, Users).
    • 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 into 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.

    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:

    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": "",
        "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: "",
        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
      },
    });

    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 a limit of 1,000 requests at a time. 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 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 issues
    • SPPRequestError – Network or HTTP-level failures
    • 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.

    Example

    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.

    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.


    © 2025 The REDspace · Apache-2.0