Skip to content

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.

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)
  1. 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 XRPL
    selectedTier: 'builder', // builder | growth | scale | enterprise
    selectedNetworks: ['xrpl'],
    });
    console.log(res.depositInstructions);
    // { walletAddress, tokenId, amount, chain }
  2. Send the deposit on-chain.

  3. 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.

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 }
const renewal = await subscription.renew({
appId: 'app_abc',
additionalDays: 30, // optional — server config default if omitted
});
console.log(renewal.newExpiresAt);

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).

// 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.
// 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 }
const all = await subscription.list();
const active = await subscription.listByStatus('active');
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