Subscriptions client
Using a Smart Engines cluster isn’t free-for-all: each smart-app holds a subscription that sets its daily API quota and which networks it can touch. SubscriptionClient is the SDK surface for that whole lifecycle — from “I want in” to “renew my access” to “show me what I’ve used.”
/api/v3/subscription/* is reachable through the public gateway (unlike the retired validator raw tier — SDK 8.0), so SubscriptionClient is a small standalone client you point at the gateway directly with createHttpClient:
import { SubscriptionClient, createHttpClient } from '@hsuite/smart-engines-sdk';
const http = createHttpClient({ baseUrl: 'https://gateway.testnet.hsuite.network/api/v3' });const subscription = new SubscriptionClient(http);For the conceptual model (tiers, deposits, NFT memberships), see the Subscriptions section.
The lifecycle
Section titled “The lifecycle”sequenceDiagram
participant App as Your app
participant Sub as subscription
participant V as Cluster
participant Chain as XRPL
App->>Sub: request({ appId, tier, chain, ... })
Sub->>V: POST /subscription/request
V-->>Sub: depositInstructions { walletAddress, tokenId, amount }
App->>Chain: send the deposit
App->>Sub: mintNft(appId)
Sub->>V: POST /subscription/mint/:appId
V-->>Sub: { subscriptionId, expiresAt }
Note over App,V: subscription active — start making calls
App->>Sub: renew({ appId }) (later, before expiry)
Requesting a subscription
Section titled “Requesting a subscription”-
Request a subscription:
const res = await subscription.request({appId: 'app_abc',appName: 'My Smart App',developerAccountId: 'rDeveloper...',chain: 'xrpl', // the HSUITE deposit is paid on XRPLselectedTier: 'builder', // builder | growth | scale | enterpriseselectedNetworks: ['xrpl'],});console.log(res.depositInstructions);// { walletAddress, tokenId, amount, chain } -
Send the deposit on-chain.
-
Finalise by minting the subscription NFT:
const mint = await subscription.mintNft('app_abc');console.log(mint.expiresAt);
mintNft is idempotent and reports what actually happened: a 200 carries nftId, nftSerial and offerTxId (the sell offer addressed to your wallet — accept it to take ownership); a 202 with minted: false means a mint is already under way (poll getStatus); 409 means the platform refused (for example the issuer already holds an NFT for the app); 503 is retryable (the validators’ signing or the issuer’s reserve is not ready); 502 means the on-chain mint failed. It never answers 200 without an NFT.
Checking status and balance
Section titled “Checking status and balance”const status = await subscription.getStatus('app_abc');// status.status: 'pending_deposit' | 'deposit_confirmed' | 'active' | 'expired' | 'cancelled' | 'not_found'
const balance = await subscription.getBalance('app_abc');// { appId, subscriptionId, status, remainingBalance, expiresAt }Renewing
Section titled “Renewing”const renewal = await subscription.renew({ appId: 'app_abc', additionalDays: 30, // optional — server config default if omitted});console.log(renewal.newExpiresAt);Discovering tiers (don’t hardcode them)
Section titled “Discovering tiers (don’t hardcode them)”Always fetch tiers from the network rather than baking names into your app — pricing, quotas, and features come straight from the cluster:
const tiers = await subscription.getTiers();console.log(tiers.map((t) => `${t.name}: $${t.priceUsd}/mo`));getConfig() returns the broader subscription configuration (deposit amount, lock duration, renewal window, HSUITE token ids, and the tier list).
Changing tiers
Section titled “Changing tiers”// Upgrade — returns top-up deposit instructions; flips once the on-chain top-up confirms.const up = await subscription.upgrade({ appId: 'app_abc', toTier: 'scale' });console.log(up.depositInstructions);
// Downgrade — scheduled, takes effect at the next renewal (no mid-lock refund).const down = await subscription.downgrade({ appId: 'app_abc', toTier: 'growth' });
// Cancel a pending (not-yet-applied) tier change.await subscription.cancelTierChange({ appId: 'app_abc' });// Changed your mind before paying? Discard the unpaid app. status reads `cancelled`, and it leaves the pending list.await subscription.cancel({ appId: 'app_abc' });// 409 `deposit_present: ...` if any HST already reached the deposit account: that app was paid for.Usage and billing
Section titled “Usage and billing”// Today's consumption by category (owner-only).const usage = await subscription.getUsage('app_abc');// { appId, total, byCategory: { database, functions, messaging, storage, ipfs, snapshots, other }, ... }
// Usage with overage info + upgrade suggestion (owner-only).const usageStatus = await subscription.getUsageStatus('app_abc');// { usagePercent, inGracePeriod, blocked, suggestUpgrade, projectedDaysUntilLimit, ... }
// Billing history, newest first: deposits (`subscribe`), `upgrade`s, `topup`s, `renew`als and float `credit`s. An// entry is an order that took a payment (amount, the ledger transactions, when the last one closed); a quote nobody paid// is not one. Pagination (limit 1-200) + ISO-8601 range filtering on `timestamp`.const billing = await subscription.getBilling('app_abc', { limit: 50, from: '2026-01-01T00:00:00Z' });// { appId, entries: [{ id, operation, status, tier, asset, amount, due, txHashes, account, timestamp, ... }], total, hasMore }Listing
Section titled “Listing”const all = await subscription.list();const active = await subscription.listByStatus('active');Method reference
Section titled “Method reference”| Method | Endpoint (/api/v3/...) |
Purpose |
|---|---|---|
request(req) |
subscription/request |
Create a deposit wallet + instructions |
mintNft(appId) |
subscription/mint/:appId |
Mint the subscription NFT after deposit |
getStatus(appId) |
subscription/status/:appId |
Subscription status: tier in every state, pendingTierChange while one is scheduled, currentOrder while pending_deposit. An app you own whose subscription the serving node has not replicated yet is a retryable 503 (subscription_not_replicated) rather than hasSubscription: false, for at most 10 minutes (one create-intent window) after the node first held the app |
getBalance(appId) |
subscription/balance/:appId |
Remaining collateral balance |
renew(req) |
subscription/renew |
Extend the lock period |
getTiers() |
subscription/tiers |
Canonical tier list. priceUsd is the monthly price in US dollars; quote (when a rate is available) is that price in whole HST at the live HSUITE/USD rate, with the rate and its source. Indicative: the order locks the price paid |
getConfig() |
subscription/config |
Full subscription config |
list() |
subscription/list |
Your subscriptions (each entry carries its tier) |
listByStatus(status) |
subscription/list/status/:status |
Filter by status |
upgrade(req) |
subscription/upgrade |
Request an upgrade (top-up) |
downgrade(req) |
subscription/downgrade |
Schedule a downgrade |
cancelTierChange(req) |
subscription/tier-change/cancel |
Cancel a pending tier change |
cancel(req) |
subscription/cancel |
Discard an app that was never paid for: only while pending_deposit, no payment counted on an order and nothing paid on the validator’s own read of the deposit account’s validated payment history, otherwise 409 (the message begins not_pending_deposit:, payment_received: or deposit_present:; a 503 ledger_unreadable: is retryable). Becomes cancelled on every cluster; idempotent; owner-only |
getUsage(appId) |
subscription/usage/:appId |
Today’s usage by category |
getUsageStatus(appId) |
subscription/usage/status/:appId |
Usage + overage + suggestions |
getBilling(appId, opts?) |
subscription/billing/:appId |
Billing history: the orders that took a payment and the float credits, newest first |