TypeScript / Node.js SDK
Install and use the official PacSpace SDK for TypeScript and Node.js. Zero dependencies, fully typed, built for Node.js 18+.
The official PacSpace SDK for TypeScript and Node.js. It wraps the Balance API in a typed, ergonomic client that handles authentication, retries, response parsing, and webhook verification — so you can focus on your integration.
- Zero dependencies — uses native
fetch(Node.js 18+) - Fully typed — complete TypeScript definitions for every request and response
- Automatic retries — exponential backoff on transient errors
- Environment detection — auto-selects Sandbox or Production from your API key prefix
- MIT licensed — view on npm
Installation
npm install @pacspace-io/sdk
Requires Node.js 18+ and TypeScript 5+ (for full type inference).
Quick Setup
import { PacSpace } from '@pacspace-io/sdk';
const pac = new PacSpace({
apiKey: process.env.PACSPACE_API_KEY,
});
// Record a delta
const delta = await pac.balance.emit('cust_123', -42.50, 'usage_charge');
// Derive the balance
const { computedBalance } = await pac.balance.derive('cust_123');
The SDK reads your API key prefix to detect the environment automatically and routes requests to the correct API endpoint:
pk_test_*keys → Sandbox API endpoint (dedicated sandbox infrastructure)pk_live_*keys → Production API endpoint (api.pacspace.io)
The SDK handles URL routing transparently — no extra configuration needed.
URL Auto-Routing
When you use a pk_test_* key, the SDK automatically routes to the Sandbox API endpoint. When you use a pk_live_* key, it routes to the Production API endpoint (api.pacspace.io). This ensures your requests always hit the correct environment's dedicated infrastructure.
Overriding Endpoints
You can override the default endpoints if needed:
const pac = new PacSpace({
apiKey: 'pk_test_PUBLIC.SECRET',
sandboxUrl: 'https://custom-sandbox.example.com', // Override sandbox endpoint
productionUrl: 'https://api.pacspace.io', // Override production endpoint
});
Note: In most cases, you won't need to override endpoints. The SDK's automatic routing based on API key prefix is sufficient for all standard use cases.
Configuration
Pass options when creating the client:
const pac = new PacSpace({
apiKey: 'pk_live_PUBLIC.SECRET', // Required - SDK auto-routes based on prefix
maxRetries: 2, // Retries on transient errors (default: 2)
timeout: 30000, // Request timeout in ms (default: 30000)
webhookSecret: 'whsec_...', // Enables pac.webhooks (optional)
// Optional endpoint overrides (usually not needed):
// sandboxUrl: 'https://custom-sandbox.example.com',
// productionUrl: 'https://api.pacspace.io',
});
Options Reference
| Option | Type | Default | Description |
|---|---|---|---|
apiKey | string | required | Your PacSpace API key. SDK auto-routes based on prefix (pk_test_* → Sandbox, pk_live_* → Production) |
sandboxUrl | string | Auto-detected | Sandbox API endpoint (override only if needed) |
productionUrl | string | https://api.pacspace.io | Production API endpoint (override only if needed) |
maxRetries | number | 2 | Max retries on 5xx and network errors |
timeout | number | 30000 | Request timeout in milliseconds |
webhookSecret | string | — | Your webhook signing secret. Enables pac.webhooks |
Balance API
All Balance API methods are available on pac.balance.
emit()
Record a delta for a customer. Returns immediately with a QUEUED status.
const delta = await pac.balance.emit(
'cust_123', // customerId
-42.50, // delta amount (positive = increase, negative = decrease)
'usage_charge', // reason (for audit trail)
{
referenceId: 'inv_001', // your internal reference (optional)
metadata: { plan: 'growth' }, // arbitrary metadata (optional)
idempotencyKey: 'idem_abc', // prevent duplicate processing (optional)
}
);
console.log(delta.receiptId); // store for later verification
console.log(delta.status); // 'QUEUED'
emitAndWait()
Record a delta and block until it reaches a terminal status (VERIFIED or FAILED). Useful for synchronous workflows.
const verified = await pac.balance.emitAndWait(
'cust_123',
-42.50,
'usage_charge',
{
timeout: 30_000, // max wait time in ms (default: 60000)
pollInterval: 1000, // polling frequency in ms (default: 2000)
}
);
console.log(verified.status); // 'VERIFIED' or 'FAILED'
derive()
Derive a customer's balance from all verified deltas.
const result = await pac.balance.derive('cust_123');
console.log(result.computedBalance); // derived balance
console.log(result.deltasCount); // number of verified deltas
console.log(result.latestReceiptId); // use as checkpoint for next call
Paginate the deltas array with limit and offset:
const result = await pac.balance.derive('cust_123', {
limit: 50,
offset: 100,
});
console.log(result.pagination.total); // Total deltas available
The computedBalance is always derived from all verified deltas, regardless of pagination.
For large histories, use checkpointing to avoid replaying from the beginning:
const next = await pac.balance.derive('cust_123', {
startingBalance: result.computedBalance,
startingCheckpoint: result.latestReceiptId,
});
compare()
Compare your balance against a counterparty's. PacSpace derives the neutral truth and tells you who matches.
const report = await pac.balance.compare('cust_123', {
yours: 95000,
theirs: 98000,
});
console.log(report.matchesYours); // true or false
console.log(report.matchesTheirs); // true or false
console.log(report.neutralBalance); // the independently derived balance
if (report.discrepancyReport) {
console.log(report.discrepancyReport.amount);
console.log(report.discrepancyReport.resolution);
}
receipt()
Generate a verifiable receipt containing all verified deltas for a customer.
const receipt = await pac.balance.receipt('cust_123');
console.log(receipt.finalBalance);
console.log(receipt.deltasCount);
console.log(receipt.verification.itemHashes); // content fingerprints
checkpoint()
Commit a period-end checkpoint. Creates an immutable proof root over all verified deltas in the billing window.
const checkpoint = await pac.balance.checkpoint('cust_123', {
period: '2026-02', // YYYY-MM format (default: current period)
});
console.log(checkpoint.merkleRoot); // proof root — include in your invoice
console.log(checkpoint.deltaCount);
console.log(checkpoint.status); // 'QUEUED'
Omit customerId to checkpoint all customers:
const all = await pac.balance.checkpoint();
listCheckpoints()
Retrieve committed checkpoints with optional filtering by customer or period.
const { checkpoints } = await pac.balance.listCheckpoints({
period: '2026-02',
limit: 10,
});
for (const cp of checkpoints) {
console.log(cp.checkpointId, cp.merkleRoot, cp.status);
}
Filter by customer:
const { checkpoints } = await pac.balance.listCheckpoints({
customerId: 'cust_123',
period: '2026-01',
limit: 20,
offset: 0,
});
deltaStatus()
Check the processing status of a previously emitted delta. For batch anchors, returns all deltas in the anchor.
const status = await pac.balance.deltaStatus('anc_abc123');
console.log(status.status); // 'ANCHORED'
console.log(status.deltaCount); // 1 for single emit, N for batch
// Batch anchors include all deltas
for (const d of status.deltas) {
console.log(d.customerId, d.delta);
}
emitBatch()
Record up to 100 deltas in a single request. Each result includes an index (position in your input array) and an itemHash (individual content hash for Merkle verification).
const result = await pac.balance.emitBatch([
{ customerId: 'cust_123', delta: -100, reason: 'usage' },
{ customerId: 'cust_456', delta: -200, reason: 'usage' },
]);
console.log(result.totalQueued); // 2
// Per-item results with index correlation
for (const r of result.results) {
console.log(r.index, r.customerId, r.itemHash, r.status);
}
// Efficiency summary — shows how deltas were packed into verified operations
console.log(result._efficiency?.message);
// "2 delta(s) packed into 2 verified operation(s)"
Webhook Delivery History
const { deliveries } = await pac.balance.listWebhookDeliveries({
status: 'failed',
});
// Retry a failed delivery
await pac.balance.retryWebhook(deliveries[0].eventId);
Customer Ledgers
Every unique customerId you pass to emit() automatically creates an isolated ledger. Use these methods to list and inspect your customer ledgers.
customers()
List all customer ledgers for your account:
const { customers, pagination } = await pac.balance.customers();
for (const c of customers) {
console.log(c.customerId, c.totalDeltas, c.ledgerIdentifier);
}
// Search and paginate
const filtered = await pac.balance.customers({
search: 'partner_',
limit: 10,
page: 2,
});
customer()
Get full detail for a specific customer's ledger:
const ledger = await pac.balance.customer('cust_001');
console.log(ledger.ledgerIdentifier); // 0xA1b2...Ef34
console.log(ledger.computedBalance); // 4500.00
console.log(ledger.totalDeltas); // 42
// Access recent activity
for (const item of ledger.recentActivity.items) {
console.log(item.amount, item.reason, item.status);
}
// Paginate activity
const page2 = await pac.balance.customer('cust_001', {
deltaPage: 2,
deltaLimit: 20,
});
Privacy note: PacSpace stores only the derived ledger identifier for each customer. Store the mapping between your customer IDs and your internal records in your own system.
Receipts & Verification
Generate receipt-ready proof data for a customer's period. Checkpoint first, then fetch the receipt.
receipt()
Generate a receipt for embedding in an invoice:
const checkpoint = await pac.balance.checkpoint('cust_001', { period: '2026-02' });
const receipt = await pac.balance.receipt('cust_001', { period: '2026-02' });
console.log(receipt.verification?.itemsRoot ?? receipt.receiptId ?? checkpoint.merkleRoot); // Include in your invoice
console.log(receipt.finalBalance); // The verified ending balance
console.log(receipt.deltasCount); // Number of verified deltas in the period
The response includes everything you need:
// receipt.receiptId / itemsRoot — proof root for the period
// receipt.deltasCount — number of verified deltas
// receipt.finalBalance — balance at end of period
// receipt.deltas[] — all verified deltas with reason, referenceId, itemHash
// receipt.verification — verification metadata
Default period is the current month. Pass period to generate proof for a specific period.
Full Receipt Workflow
// 1. Record deltas throughout the month
await pac.balance.emit({ customerId: 'cust_001', delta: -150, reason: 'api_usage' });
// 2. Lock the period
const checkpoint = await pac.balance.checkpoint('cust_001', { period: '2026-02' });
// 3. Generate receipt
const receipt = await pac.balance.receipt('cust_001', { period: '2026-02' });
// 4. Include in your invoice
const invoice = {
total: Math.abs(receipt.finalBalance),
proofRoot: receipt.receiptId ?? receipt.verification?.itemsRoot ?? checkpoint.merkleRoot,
verifyUrl: `https://balance-api.pacspace.io/api/v1/verify/${receipt.receiptId ?? checkpoint.merkleRoot}`,
};
See the Receipts & Verification guide for the complete workflow including the public verify endpoint and dispute handling.
Webhook Verification
The SDK can verify incoming webhook signatures to ensure they came from PacSpace and haven't been tampered with.
Requires a
webhookSecretin the constructor. Get your webhook secret from the PacSpace dashboard.
verify()
Verify a signature and parse the event:
const pac = new PacSpace({
apiKey: process.env.PACSPACE_API_KEY,
webhookSecret: process.env.PACSPACE_WEBHOOK_SECRET,
});
const event = pac.webhooks.verify(
req.headers['x-pacspace-signature'],
req.headers['x-pacspace-timestamp'],
rawBody, // raw string body, before JSON parsing
);
if (event.event === 'delta.verified') {
console.log(event.data.receiptId);
}
verifyFromHeaders()
Convenience method that extracts the signature and timestamp from a headers object:
const event = pac.webhooks.verifyFromHeaders(req.headers, rawBody);
middleware()
Drop-in Express/Connect middleware that verifies the signature and attaches the parsed event to the request:
import express from 'express';
const app = express();
// Capture the raw body (required for signature verification)
app.use('/webhooks', express.json({
verify: (req, _res, buf) => {
req.rawBody = buf.toString();
},
}));
app.post('/webhooks/pacspace', pac.webhooks.middleware(), (req, res) => {
const event = req.pacspaceEvent;
switch (event.event) {
case 'delta.verified':
console.log('Delta verified:', event.data.receiptId);
break;
case 'checkpoint.verified':
console.log('Checkpoint verified:', event.data.proofHash);
break;
}
res.status(200).json({ received: true });
});
Webhook Event Types
| Event | When It Fires |
|---|---|
delta.verified | A delta has been independently verified |
delta.stored | Delta fingerprints have been permanently committed |
checkpoint.verified | A period-end checkpoint has been verified |
fact.verified | A fact has been committed and verified |
record.transferred | Ownership of a record has been transferred and verified |
All event types are fully typed. Use event.event to narrow the type:
if (event.event === 'delta.verified') {
// event.data is typed as DeltaVerifiedPayload
console.log(event.data.delta.customerId);
console.log(event.data.proof.proofHash);
}
Error Handling
The SDK throws typed errors you can catch by class. Every error extends PacSpaceError.
import {
PacSpace,
PlanLimitExceededError,
RateLimitError,
ValidationError,
} from '@pacspace-io/sdk';
try {
await pac.balance.emit('cust_123', -1000, 'charge');
} catch (err) {
if (err instanceof PlanLimitExceededError) {
console.log('Plan limit reached — upgrade or wait for next period');
} else if (err instanceof RateLimitError) {
console.log(`Retry after ${err.retryAfter} seconds`);
} else if (err instanceof ValidationError) {
console.log(`Invalid request: ${err.message}`);
}
}
Error Classes
| Error Class | Status | When |
|---|---|---|
ValidationError | 400 | Invalid request data |
InvalidApiKeyError | 401 | Missing or invalid API key |
PlanLimitExceededError | 402 | Free plan limit reached or no active subscription |
NotFoundError | 404 | Customer or resource not found |
ContractNotDeployedError | 412 | No environment activated |
RateLimitError | 429 | Rate limit exceeded (check err.retryAfter) |
TimeoutError | — | Polling timed out waiting for verification |
WebhookVerificationError | — | Webhook signature verification failed |
Every error includes:
err.message— human-readable descriptionerr.statusCode— HTTP status code (or0for client-side errors)err.code— machine-readable code (e.g.,PLAN_LIMIT_EXCEEDED)err.requestPath— the API path that triggered the error
Response Unwrapping
The PacSpace API wraps all responses in a standard envelope:
{
"success": true,
"data": { "computedBalance": 95000, "..." }
}
The SDK unwraps this automatically. You always receive the data object directly:
// API returns: { success: true, data: { computedBalance: 95000 } }
// SDK returns: { computedBalance: 95000 }
const result = await pac.balance.derive('cust_123');
console.log(result.computedBalance); // 95000
On error responses, the SDK throws a typed error instead of returning the envelope.
Automatic Retries
The SDK retries automatically on transient errors:
| Condition | Retried? | Strategy |
|---|---|---|
| Network failure | Yes | Exponential backoff |
| 500, 502, 503, 504 | Yes | Exponential backoff |
| 429 (rate limited) | Yes | Respects Retry-After header |
| 400, 401, 402, 404 | No | Throws immediately |
Default: 2 retries with exponential backoff and jitter. Configure with maxRetries:
const pac = new PacSpace({
apiKey: '...',
maxRetries: 5, // up to 5 retries on transient errors
});
Set maxRetries: 0 to disable retries entirely.
Request Cancellation
Cancel in-flight requests using an AbortSignal:
const controller = new AbortController();
// Cancel after 5 seconds
setTimeout(() => controller.abort(), 5000);
try {
await pac.balance.derive('cust_123', {
signal: controller.signal,
});
} catch (err) {
if (err.code === 'CANCELLED') {
console.log('Request was cancelled');
}
}
Package Details
| Detail | Value |
|---|---|
| npm package | @pacspace-io/sdk |
| Version | 0.1.0 |
| Dependencies | None |
| Minimum Node.js | 18+ |
| TypeScript | 5+ (recommended) |
| License | MIT |
| Source | GitHub |
Next Steps
- Quick Start Guide — go from zero to your first verified delta in 5 minutes
- Balance API Reference — detailed endpoint documentation
- Webhook Setup — configure webhooks for real-time notifications
- Error Handling — full error reference and retry strategies