The Pi Labs JavaScript SDK provides convenient, type-safe access to the Pi Client REST API from server-side TypeScript or JavaScript applications. This guide covers installation, basic usage, configuration, and advanced features. Documentation is also available at the GitHub repository. The SDK supports a wide range of JavaScript runtimes:

Web Browsers

Modern browsers including Chrome, Firefox, Safari, and Edge

Node.js

Node.js 18 LTS or later (non-EOL versions)

Alternative Runtimes

Deno v1.28.0+, Bun 1.0+, Cloudflare Workers, Vercel Edge Runtime

Testing & Frameworks

Jest 28+ with “node” environment, Nitro v2.6+
React Native is not currently supported. If you need support for additional runtime environments, please contact us.

Quickstart

1

Get your API key

Retrieve your API key from your account page.
2

Install the SDK using your preferred package manager:
npm install withpi
3

Initialize the client

Create a new client instance with your API key. If apiKey is not specified, the SDK will look for the WITHPI_API_KEY environment variable.
import PiClient from 'withpi';

const pi = new PiClient({
  apiKey: process.env.WITHPI_API_KEY,
});
4

Make your first scoring request

Issue a request to Pi Scorer:
const scores = await pi.scoringSystem.score({
  llm_input: "What are some good day trips from Milan by train?",
  llm_output: "Milan is an excellent hub for day trips by train. You can take a short ride to the stunning shores of Lake Como to explore picturesque towns like Bellagio and Varenna. Alternatively, the historic hilltop city of Bergamo is another charming and easily accessible option. For a spectacular alpine adventure, consider the Bernina Express scenic train journey through the Swiss Alps. Cities like Turin and Verona are also just an hour or two away via high-speed train.",
  scoring_spec: [
    { question: "Does the response maintain a professional tone?" },
    { question: "Did the response fulfill the intent of the user's query?" },
    { question: "Did the response only present data relevant to the user's query?" }
  ],
});

console.log('Total Score:', scores.total_score);
console.log('Question Scores:', scores.question_scores);
You should receive a response with the scores Pi Scorer assigned to the generation.
Note that because the SDK is generated with Stainless, function parameters use snake_case names rather than camelCase.

TypeScript Support

The SDK includes comprehensive TypeScript definitions for type safety:
import PiClient from 'withpi';

const pi = new PiClient();

// Type-safe parameters
const params: PiClient.ScoringSystemScoreParams = {
  llm_input: "What are some good day trips from Milan by train?",
  llm_output: "Milan is an excellent hub for day trips by train. You can take a short ride to the stunning shores of Lake Como to explore picturesque towns like Bellagio and Varenna. Alternatively, the historic hilltop city of Bergamo is another charming and easily accessible option. For a spectacular alpine adventure, consider the Bernina Express scenic train journey through the Swiss Alps. Cities like Turin and Verona are also just an hour or two away via high-speed train.",
  scoring_spec: [
    {
      label: "Professional Tone",
      question: "Does the response maintain a professional tone?"
    },
    {
      label: "Intent Fulfillment", 
      question: "Did the response fulfill the intent of the user's query?"
    },
    {
      label: "Relevance Check",
      question: "Did the response only present data relevant to the user's query?"
    }
  ],
};

// Type-safe response
const scoringSystemMetrics: PiClient.ScoringSystemMetrics = 
  await pi.scoringSystem.score(params);

Configuration Options

Client Initialization

Configure the client with various options for your specific use case:
const pi = new PiClient({
  timeout: 30 * 1000,     // 30 seconds (default is 1 minute)
  maxRetries: 3,          // Maximum retry attempts (default is 2)
  // Additional configuration options...
});

Timeouts

Configure request timeouts globally or per request:
// Configure timeout for all requests
const pi = new PiClient({
  timeout: 20 * 1000, // 20 seconds
});

Retries

The SDK automatically retries certain errors with exponential backoff:
  • Connection errors
  • 408 Request Timeout
  • 409 Conflict
  • 429 Rate Limit
  • 500+ Internal Server errors
// Configure retries for all requests
const pi = new PiClient({
  maxRetries: 0, // Disable retries
});

Error Handling

The SDK throws subclasses of APIError for different scenarios:
try {
  const result = await pi.scoringSystem.score({
    llm_input: "What are some good day trips from Milan by train?",
    llm_output: "Milan is an excellent hub for day trips by train. You can take a short ride to the stunning shores of Lake Como to explore picturesque towns like Bellagio and Varenna. Alternatively, the historic hilltop city of Bergamo is another charming and easily accessible option.",
    scoring_spec: [
      { question: "Does the response maintain a professional tone?" },
      { question: "Did the response fulfill the intent of the user's query?" },
      { question: "Did the response only present data relevant to the user's query?" }
    ],
  });
} catch (err) {
  if (err instanceof PiClient.APIError) {
    console.log('Status:', err.status);    // e.g., 400
    console.log('Error Type:', err.name);  // e.g., BadRequestError
    console.log('Headers:', err.headers);  // Response headers
  } else {
    throw err; // Re-throw non-API errors
  }
}

Error Types

Status CodeError TypeDescription
400BadRequestErrorInvalid request parameters
401AuthenticationErrorInvalid or missing API key
403PermissionDeniedErrorInsufficient permissions
404NotFoundErrorResource not found
422UnprocessableEntityErrorRequest validation failed
429RateLimitErrorRate limit exceeded
500+InternalServerErrorServer-side errors
N/AAPIConnectionErrorNetwork connectivity issues
Requests that time out throw an APIConnectionTimeoutError and are automatically retried according to your retry configuration.

Advanced Usage

Accessing Raw Response Data

Access the underlying Response object for headers and other metadata:
const response = await pi.scoringSystem
  .score({
    llm_input: "What are some good day trips from Milan by train?",
    llm_output: "Milan is an excellent hub for day trips by train. You can take a short ride to the stunning shores of Lake Como to explore picturesque towns like Bellagio and Varenna.",
    scoring_spec: [{ question: "Does the response maintain a professional tone?" }],
  })
  .asResponse();

console.log(response.headers.get('X-My-Header'));
console.log(response.statusText);

Custom and Undocumented Endpoints

You can issue HTTP requests to any endpoint, including undocumented ones, via the SDK client:
// Custom endpoint
await pi.post('/some/path', {
  body: { some_prop: 'foo' },
  query: { some_query_arg: 'bar' },
});

// Undocumented parameters
pi.foo.create({
  foo: 'my_param',
  bar: 12,
  // @ts-expect-error baz is not yet public
  baz: 'undocumented option',
});
Extra parameters in GET requests become query parameters, while other HTTP methods send them in the request body.

Custom Fetch Implementation

If needed, you can provide a custom fetch function for logging, middleware, or proxy support:
import { fetch } from 'undici';
import PiClient from 'withpi';

const client = new PiClient({
  fetch: async (url: RequestInfo, init?: RequestInit): Promise<Response> => {
    console.log('Making request to:', url);
    const response = await fetch(url, init);
    console.log('Response status:', response.status);
    return response;
  },
});
You can set DEBUG=true as an environment variable to automatically log all requests and responses for debugging.

HTTP Agent Configuration

By default, this library uses a stable agent for all http/https requests to reuse TCP connections, eliminating many TCP & TLS handshakes and shaving around 100ms off most requests. If you would like to disable or customize this behavior, for example to use the API behind a proxy, you can pass an httpAgent which is used for all requests (be they http or https), for example:
import http from 'http';
import { HttpsProxyAgent } from 'https-proxy-agent';

// Global configuration
const client = new PiClient({
  httpAgent: new HttpsProxyAgent(process.env.PROXY_URL),
});

// Per-request configuration
await client.scoringSystem.score({
  llm_input: "What are some good day trips from Milan by train?",
  llm_output: "Milan is an excellent hub for day trips by train. You can take a short ride to the stunning shores of Lake Como.",
  scoring_spec: [{ question: "Does the response maintain a professional tone?" }]
}, {
  httpAgent: new http.Agent({ keepAlive: false }),
});

Next Steps