---
title: Node.js
slug: sdks/server-sdks/nodejs
excerpt: >-
  Official TypeScript / Node.js SDK for the Pine Labs Online Payment Gateway —
  published on npm as pinelabs-node.
hidden: false
sidebar_order: 2
metadata:
  title: "Node.js SDK — Official TypeScript SDK | Pine Labs Gateway"
  description: >
    Official Pine Labs Node.js/TypeScript SDK (pinelabs-node). Install via npm, authenticate with API keys, and integrate payment APIs in your Node.js backend with full type safety.
  keywords: "Node.js SDK, TypeScript SDK, pinelabs-node, npm package, server SDK, Node backend integration, TypeScript payment SDK"
  robots: index
---
`pinelabs-node` is the official TypeScript / Node.js SDK for the **Pine Labs Online Payment Gateway** (Plural). It is generated from the same OpenAPI spec that powers this documentation, so every operation, request, and response model is fully typed.
 
- **Package:** [`pinelabs-node`](https://www.npmjs.com/package/pinelabs-node) on npm
- **Runtime:** Node.js ≥ 18
- **Module format:** ESM-only
 
<Callout type="info" title="ESM-only">
  This package ships as ES modules. Use `import` from a project with `"type": "module"`, or use dynamic `import()`. Calling `require("pinelabs-node")` in CommonJS will fail with `ERR_PACKAGE_PATH_NOT_EXPORTED`.
</Callout>
 
## Install
 
<CodeTabs>
<Tab label="npm">
    ```bash
    npm install pinelabs-node
    ```
</Tab>
<Tab label="pnpm">
    ```bash
    pnpm add pinelabs-node
    ```
</Tab>
<Tab label="yarn">
    ```bash
    yarn add pinelabs-node
    ```
</Tab>
</CodeTabs>
 
## Quickstart
 
The Pine Labs API uses **OAuth2 with the `client_credentials` grant**. Exchange your `client_id` / `client_secret` for an access token, then pass it to `PinelabsApiClient`.
 
```ts
import { PinelabsApiClient } from "pinelabs-node";
 
const baseUrl = "https://pluraluat.v2.pinepg.in"; // UAT
// const baseUrl = "https://api.pluralpay.in";     // Production
 
// 1. Get a token (the auth call itself does not need a bearer token)
const auth = new PinelabsApiClient({ baseUrl, token: "" });
 
const tokenResponse = await auth.authentication.generateToken({
  grant_type: "client_credentials",
  client_id: process.env.PINELABS_CLIENT_ID!,
  client_secret: process.env.PINELABS_CLIENT_SECRET!,
});
 
// 2. Build an authenticated client
const client = new PinelabsApiClient({
  baseUrl,
  token: tokenResponse.access_token,
});
 
// 3. Call any operation
const order = await client.orders.createOrder({
  merchant_order_reference: "order-001",
  order_amount: { value: 50000, currency: "INR" }, // ₹500.00
  // ...see the API reference for the full schema
});
 
console.log(order);
```
 
## Environments
 
| Environment | Base URL                            |
| ----------- | ----------------------------------- |
| UAT         | `https://pluraluat.v2.pinepg.in`    |
| Production  | `https://api.pluralpay.in`          |
 
Pass the URL via `baseUrl`. The exported `PinelabsApiEnvironment` enum currently only contains `Production`; for UAT, set `baseUrl` explicitly.
 
## Auto-refreshing token
 
For long-running services, cache the token and refresh it before it expires. Pass a function instead of a string to `token` — it will be invoked on every request, **including the auth call itself**, so guard against re-entrancy:
 
```ts
import { PinelabsApiClient } from "pinelabs-node";
 
const baseUrl = "https://pluraluat.v2.pinepg.in";
const auth = new PinelabsApiClient({ baseUrl, token: "" });
 
let cached: { value: string; expiresAt: number } | undefined;
let fetching = false;
 
async function getToken(): Promise<string | undefined> {
  // Re-entrancy guard: the auth endpoint itself doesn't need a token.
  if (fetching) return undefined;
 
  // Refresh ~30s before expiry
  if (cached && Date.now() < cached.expiresAt - 30_000) {
    return cached.value;
  }
 
  fetching = true;
  try {
    const r = await auth.authentication.generateToken({
      grant_type: "client_credentials",
      client_id: process.env.PINELABS_CLIENT_ID!,
      client_secret: process.env.PINELABS_CLIENT_SECRET!,
    });
    cached = {
      value: r.access_token,
      expiresAt: Date.now() + r.expires_in * 1000,
    };
    return cached.value;
  } finally {
    fetching = false;
  }
}
 
const client = new PinelabsApiClient({ baseUrl, token: getToken });
 
// Every call now picks up a fresh token automatically.
await client.orders.getOrderById({ order_id: "ord-123" });
```
 
<Callout type="warning" title="Don't skip the re-entrancy guard">
  Without the `fetching` flag the token supplier will recurse into itself when the auth call fires — your process will hang silently with no output and no error.
</Callout>
 
## Sub-clients
 
`PinelabsApiClient` exposes one sub-client per API tag. All are lazy-loaded.
 
| Sub-client                            | Purpose                                            |
| ------------------------------------- | -------------------------------------------------- |
| `client.authentication`               | OAuth token generation                             |
| `client.orders`                       | Create, capture, cancel, and fetch orders          |
| `client.refunds`                      | Create and look up refunds                         |
| `client.settlements`                  | Settlement reports + UTR lookup                    |
| `client.checkout`                     | Hosted-checkout related operations                 |
| `client.paymentLinks`                 | Single + bulk payment links                        |
| `client.cardPayments`                 | Direct card payment + OTP flow                     |
| `client.bnpl`                         | Buy-Now-Pay-Later eligibility and flows            |
| `client.convenienceFee`               | Convenience-fee config and computation             |
| `client.eChallans`                    | Government e-challan integration                   |
| `client.applePay`                     | Apple Pay session + decryption                     |
| `client.internationalPayments`        | Cross-border (DCC / MCC) payments                  |
| `client.customers`                    | Customer profile management                        |
| `client.tokenization`                 | Card / network tokenization                        |
| `client.payouts`                      | Payouts: balance, create, cancel, list             |
| `client.subscriptionsPlans`           | Recurring-billing plans                            |
| `client.subscriptionsSubscriptions`   | Subscription lifecycle                             |
| `client.subscriptionsPresentations`   | Subscription debit presentations                   |
| `client.payByPoints`                  | Loyalty / points-based payments                    |
| `client.affordabilitySuite`           | EMI / offer eligibility                            |
| `client.splitSettlements`             | Split settlements between sub-merchants            |
 
For the full operation list and request/response schemas, see the [API reference](/docs/api-reference).
 
## Error handling
 
The SDK exports two typed error classes. Catch them to distinguish HTTP failures from network timeouts:
 
```ts
import {
  PinelabsApiClient,
  PinelabsApiError,
  PinelabsApiTimeoutError,
} from "pinelabs-node";
 
try {
  await client.orders.getOrderById({ order_id: "missing" });
} catch (err) {
  if (err instanceof PinelabsApiError) {
    console.error("HTTP", err.statusCode, err.body);
  } else if (err instanceof PinelabsApiTimeoutError) {
    console.error("Request timed out");
  } else {
    throw err;
  }
}
```
 
`PinelabsApiError` exposes `statusCode`, `body` (parsed JSON when the server returns JSON), and `headers`.
 
## Per-request options
 
Every operation accepts an optional second argument for ad-hoc overrides:
 
```ts
await client.orders.createOrder(
  { merchant_order_reference: "order-002", order_amount: { value: 1000, currency: "INR" } },
  {
    timeoutInSeconds: 10,
    maxRetries: 0,
    abortSignal: AbortSignal.timeout(8000),
    headers: { "x-correlation-id": "req-abc" },
  },
);
```
 
Useful for per-call timeouts, propagating trace IDs, or disabling retries on idempotency-sensitive flows.
 
## TypeScript usage
 
The package ships full `.d.ts` declarations. Recommended `tsconfig.json` settings for consumers:
 
```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "lib": ["ES2023", "DOM", "DOM.Iterable"],
    "strict": true
  }
}
```
 
The `DOM` / `DOM.Iterable` libs are required because the SDK's fetch shims reference `Headers.entries()` and `HeadersIterator`.
 
## Source & support
 
- npm: [pinelabs-node](https://www.npmjs.com/package/pinelabs-node)
- Issues: [github.com/plural-pinelabs/Pinelabs-Agentic-SDK/issues](https://github.com/plural-pinelabs/Pinelabs-Agentic-SDK/issues)
- API reference: [/docs/api-reference](/docs/api-reference)
 
For the agent-toolkit (LangChain, Vercel AI SDK, OpenAI Agents, Anthropic, Claude Agent SDK), see [`pinelabs-agent-toolkit`](https://www.npmjs.com/package/pinelabs-agent-toolkit). For the command-line interface, see [`pinelabs-cli`](https://www.npmjs.com/package/pinelabs-cli).
