Hasapla Node.js SDK
The official TypeScript SDK for Hasapla, the billing platform for AI companies.
Typed access to customer balances, customers, organizations, features, plans, subscriptions, and usage events for Node.js 18 and newer.
Installation
npm install hasapla
Initialization
Create one client and reuse it throughout your server application. Keep the API key on the server.
import { Hasapla } from "hasapla";
const hasapla = new Hasapla({
apiKey: process.env.HASAPLA_API_KEY!,
});
Balance
const balance = await hasapla.balance.get({
extCustomerId: "customer-123",
});
// { balance: 3000, unlimited: false }
Supply exactly one of extCustomerId or extOrganizationId. The SDK maps these to the API's ext_customer_id and ext_organization_id query parameters. Unlimited subscriptions return { balance: null, unlimited: true }; pending limited subscriptions return zero. This endpoint does not authorize usage or refresh expired periods.
This replaces the previous placeholder: the result is now an object rather than a number, and unlimited balances are null instead of -1.
Customers
await hasapla.customers.create({
email: "[email protected]",
name: "Example Customer",
extCustomerId: "customer-123",
});
Subscriptions
Subscribe a customer or organization to a plan. Supply exactly one of customer_uuid or organization_uuid and an interval of "month" or "year".
const subscription = await hasapla.subscriptions.subscribe({
customer_uuid: "customer-uuid",
plan_uuid: "plan-uuid",
interval: "month",
});
Free plans activate immediately. Paid plans remain pending with null period dates and no credit grant. The interval does not select a different catalog price. Subscription fields use the API's snake_case names.
Check a customer's current subscriptions:
const subscriptions = await hasapla.subscriptions.list({
customer_uuid: "customer-uuid",
current: true,
});
Use organization_uuid for an organization. The result is an array, which may be empty. Omit current to include historical subscriptions. Pagination accepts limit (1–1000, default 100) and offset (default 0).
Retrieve a subscription by UUID:
const subscription = await hasapla.subscriptions.get("subscription-uuid");
Change the plan or interval immediately:
const replacement = await hasapla.subscriptions.change("subscription-uuid", {
plan_uuid: "target-plan-uuid",
interval: "month",
activate_immediately: true,
payment_reference_id: "verified-payment-reference",
amount: 3000,
currency: "USD",
});
For paid targets, verify payment on your server first and supply the full target price and currency. For free targets, omit all three payment fields. Same-interval changes preserve the renewal date except when moving from free to paid; interval changes start a fresh period. Matching retries return the existing successor, and selecting the same plan and interval makes no changes. Scheduled changes (activate_immediately: false) currently return HTTP 409.
Usage events
await hasapla.events.create({
extCustomerId: "customer-123",
featureKey: "api_call",
});
Reserve an event before performing work, then capture it on success or release it otherwise:
const reservation = await hasapla.events.reserve({
extCustomerId: "customer-123",
featureKey: "api_call",
});
if (!reservation.key) {
throw new Error("Reservation key is missing");
}
try {
await performWork();
await hasapla.events.capture({ key: reservation.key });
} catch (error) {
await hasapla.events.release({ key: reservation.key });
throw error;
}
When the customer has exhausted the feature allowance, reserve throws a 409 error that can be checked with isFeatureLimitReachedError:
import { isFeatureLimitReachedError } from "hasapla";
try {
await hasapla.events.reserve({
extCustomerId: "customer-123",
featureKey: "api_call",
});
} catch (error) {
if (isFeatureLimitReachedError(error)) {
console.error("Feature limit reached");
} else {
throw error;
}
}
Errors
Failed API responses throw HasaplaError with the HTTP status and parsed response body.
import { HasaplaError } from "hasapla";
try {
await hasapla.customers.get({ externalId: "missing-customer" });
} catch (error) {
if (error instanceof HasaplaError) {
console.error(error.status, error.body);
}
throw error;
}
License
Apache-2.0