API Reference
Every Smart Engines cluster exposes a plain HTTP API. The SDK is the recommended way to consume it (it handles discovery, auth, retries, and validation for you), but the API is fully usable directly — this section documents the contract underneath.
Subsections
Section titled “Subsections”Base URL and prefix
Section titled “Base URL and prefix”You reach a cluster through its gateway. On testnet the canonical entrypoint is:
https://gateway.testnet.hsuite.networkThe testnet runs on HSuite-operated validators. It is not open to developers until the security audit is complete.
In practice you should discover the live cluster rather than hardcode it — see Zero-config connect.
The version lives in the URL: the API is served under /api/v3/*, except the wallet-login routes at /auth/validator/*. The public gateway routes these prefixes, each to the service that owns it:
| Prefix | Served by | Example | Notes |
|---|---|---|---|
/api/v3/{smart-apps,subscription,faucet,resources,discovery,personhood} |
Validator | /api/v3/subscription/list |
The validator routes open to developers. The validator’s other routes serve the cluster’s own services and are not reachable through the gateway |
/api/v3/baas/* |
Host | /api/v3/baas/db/:appId/:collection |
The entire BaaS surface under one namespace. The SDK’s default /host prefix reaches the same routes (/host/api/v3/baas/...) |
/api/v3/{health,cluster,ready,live,domains,dns,routing,gov,…} |
Gateway | /api/v3/health |
Health and management surface |
/auth/validator/* |
Validator | /auth/validator/challenge |
Wallet login. Not under /api and not versioned: the path is literal |
The generic-endpoint philosophy
Section titled “The generic-endpoint philosophy”Smart Engines is multi-chain, but the API is not organised by chain. There is no /api/v3/baas/auth/xrpl/challenge. Instead the endpoint is generic and the chain is a field in the request body:
curl -X POST https://gateway.testnet.hsuite.network/api/v3/baas/auth/challenge \ -H "Content-Type: application/json" \ -d '{ "chain": "xrpl", "walletAddress": "rYourAddress...", "appId": "my-app" }'Switch chains by switching the chain value — the route stays the same. Hedera and XRPL are live today; see Chains for the rest.
Authentication model
Section titled “Authentication model”The API uses Web3 challenge→verify auth:
- Request a challenge.
- Sign it with your wallet.
- Exchange the signature for a session token.
- Then send that token as
Authorization: Bearer <token>.
There are two login doors. /auth/validator/* issues the session for the validator routes (subscription, resources, smart-apps). /api/v3/baas/auth/* issues an app-bound session for the BaaS surface; the SDK’s BaasClient uses it. Full payloads in Authentication.
sequenceDiagram
participant C as Client
participant V as Cluster
C->>V: POST /auth/validator/challenge { chain, address }
V-->>C: { challenge }
C->>C: sign(challenge)
C->>V: POST /auth/validator/authenticate { chain, address, publicKey, signature, challenge }
V-->>C: { token, sessionId, validatorId, expiresAt }
C->>V: any request + Authorization: Bearer <token>
Three auth postures (there is no public write ingress)
Section titled “Three auth postures (there is no public write ingress)”Every route sits in one of three postures. A route that is not explicitly public requires a wallet-bound session bearer: there is no unauthenticated write route, and a bare POST with no bearer is rejected.
| Posture | Who may call it | Example routes |
|---|---|---|
| Public | Anyone, no credential | POST /auth/validator/challenge, POST /auth/validator/authenticate, POST /api/v3/baas/auth/challenge, GET /api/v3/health, the faucet routes |
| Session bearer (default) | A caller holding a valid Authorization: Bearer <session-token> |
/api/v3/subscription/*, /api/v3/resources/*, /api/v3/smart-apps, /api/v3/baas/entities/* |
| In-cluster only | HSuite’s own services, over the cluster’s private network. A session bearer is refused on these routes | None you can call: they are not reachable through the public gateway |
Entity creation is host-mediated (not a public validator route)
Section titled “Entity creation is host-mediated (not a public validator route)”Creating a canonical DKG entity (token / account / topic / agent) goes through the host. The public door is /api/v3/baas/entities/*, session-authenticated. The host relays the request to the validators over the cluster’s private network; the validators’ creation routes are not reachable from outside.
sequenceDiagram participant C as Client participant H as Host BaaS participant V as Validators C->>H: "POST /api/v3/baas/entities/prepare-create (Bearer)" H->>V: "relay over the private network" V-->>H: "prepared payer-funding tx(s)" H-->>C: "prepared payer-funding tx(s)" C->>C: "payer signs + submits funding tx" C->>H: "POST /api/v3/baas/entities/execute-create (Bearer)" H->>V: "relay over the private network" V-->>H: "finalized entity" H-->>C: "finalized entity"
A token, account or topic entity is always owned by the authenticated session wallet; a body-level ownerWallet is ignored. An agent entity is owned by the customer an app-owner session forwards in X-Customer-Session-Claim, or by the session wallet when no claim is sent. The session wallet is the default payer; an explicit payerAccountId opts a customer in as payer.
Endpoint groups
Section titled “Endpoint groups”The routes below are the ones reachable through the public gateway.
Authentication — /auth/validator/*
Section titled “Authentication — /auth/validator/*”| Method | Route | Auth |
|---|---|---|
POST |
/auth/validator/challenge |
Public |
POST |
/auth/validator/authenticate |
Public |
GET |
/auth/validator/session |
Bearer token |
GET |
/auth/validator/health |
Public |
Accounts, transfers, tokens
Section titled “Accounts, transfers, tokens”There are no public account, transfer or token routes on the validator. Accounts, tokens and topics are entities: you create them and move value through the host’s entity routes. See Entities.
Transaction sovereignty (prepare)
Section titled “Transaction sovereignty (prepare)”Value-moving routes hand you transaction bytes to sign locally, so your key never leaves your machine. A prepare step returns the bytes (with requiredSignatures and readyToSubmit); you sign them; an execute step adds the validators’ threshold signatures. On the public API this is the host’s two-phase routes: prepare-create / execute-create for entity creation, and :entityId/withdraw/prepare / :entityId/withdraw/execute for withdrawals. See Transactions.
Server-side broadcast + idempotency (transfer / withdraw)
Section titled “Server-side broadcast + idempotency (transfer / withdraw)”On Hedera and XRPL the validators submit the fully-signed transfer or withdraw to the chain themselves once the entity’s rules allow it, so you need no chain-specific submit code. The entity transfer and withdraw responses carry:
| Field | Type | Meaning |
|---|---|---|
broadcast |
boolean |
true when the validators broadcast this transaction server-side |
status |
string |
On-chain execution status — present only when broadcast is true (a Hedera receipt status such as SUCCESS, or XRPL tesSUCCESS) |
idempotencyKey |
string |
Withdraw prepare only: the key this withdraw claimed, echoed from your request or generated when you sent none |
To make a withdraw safely retryable, send a client-supplied idempotencyKey
(a UUID, one per withdraw intent) on .../withdraw/prepare, and send the same
value on .../withdraw/execute. On Hedera an execute without it is refused. A
retry carrying the same key returns the existing result instead of sending a
second transaction.
Replay is step-scoped, and there is an always-on safety net
Section titled “Replay is step-scoped, and there is an always-on safety net”- A prepare replay returns the prepare result; an execute replay returns
the execute result. A stored result that does not match the step fails
closed (
409 Conflict) rather than returning a broken200. - Identical withdraws collapse even under different keys. Two calls
describing the same transfer — same entity, chain, destination, amount,
token and memo — inside a 15-minute window (the default) are treated as one,
whatever
idempotencyKeyeach carried.
Smart-app deployment — /api/v3/smart-apps/*
Section titled “Smart-app deployment — /api/v3/smart-apps/*”The validator’s smart-app entity + runtime surface. The SDK’s DeploymentClient covers the host-side deployment lifecycle under /api/v3/baas/deployment/*.
| Method | Route | Purpose |
|---|---|---|
POST |
/api/v3/smart-apps |
Create a smart-app |
GET |
/api/v3/smart-apps |
List developer-scoped smart-apps |
GET |
/api/v3/smart-apps/:appId |
Get a smart-app |
PUT |
/api/v3/smart-apps/:appId |
Update a smart-app |
DELETE |
/api/v3/smart-apps/:appId |
Retire a smart-app |
GET |
/api/v3/smart-apps/stats |
Aggregate deployment stats |
BaaS — /api/v3/baas/*
Section titled “BaaS — /api/v3/baas/*”The host’s entire Backend-as-a-Service surface lives under the single /api/v3/baas/* namespace, so one ingress rule routes it to the host service. Each sub-namespace maps to an SDK BaasClient sub-client.
| Namespace | Purpose |
|---|---|
/api/v3/baas/auth/* |
Wallet challenge→verify for the host (BaaS auth) |
/api/v3/baas/db/* |
Trustless database (CRUD + Merkle proofs) |
/api/v3/baas/storage/* |
IPFS storage |
/api/v3/baas/functions/* |
Serverless functions |
/api/v3/baas/messaging/* |
Pub/sub messaging |
/api/v3/baas/customer-session/* |
Cross-chain customer-session bridge (appId-bound, see below) |
/api/v3/baas/deployment/* |
Smart-app deployment lifecycle (host side) |
/api/v3/baas/agents/* |
Smart-agent entities |
/api/v3/baas/entities/* |
Policy-bearing DKG entity creation and value movement — direct create, payer-funded prepare/execute, transfer, withdraw (token / account / topic / agent) |
/api/v3/baas/operations/* |
Status of an async (wait: false) create — poll one operation id (see “Async entity creation” below) |
/api/v3/baas/rules/* |
Rules publish / query / simulate (see below) |
/api/v3/baas/receipts/* |
Trustless epoch / doc receipts |
/api/v3/baas/pqc-attestation/* |
PQC attestation reads |
Rules — /api/v3/baas/rules/*
Section titled “Rules — /api/v3/baas/rules/*”The smart-app developer-facing rules surface. Rules are public-by-spec on HCS, so the GET reads need a session but are not limited to your own rules; mutations pull the wallet from the authenticated session.
| Method | Route | Purpose |
|---|---|---|
POST |
/api/v3/baas/rules/publish |
Validate + publish a ValidatorRules document to HCS; returns the canonical ruleRef. A publish the app relays for a customer must be an agent rule sent as { rule, creationIntent }, the customer’s signed intent for this app over exactly that rule |
GET |
/api/v3/baas/rules |
List indexed rules filtered by owner (required) and optional type |
GET |
/api/v3/baas/rules/:consensusTimestamp |
Fetch a published rule by HCS consensus timestamp |
GET |
/api/v3/baas/rules/:consensusTimestamp/versions |
List the version chain of a rule |
POST |
/api/v3/baas/rules/simulate |
Dry-run a rule against an action + context, no state persisted — returns the per-action validation result |
Entities — /api/v3/baas/entities/*
Section titled “Entities — /api/v3/baas/entities/*”The session-authenticated public front door for canonical DKG entities. Ownership follows the rule in Entity creation is host-mediated. The direct create* routes create-and-finalize in one hop; the prepare-create / execute-create split is the payer-funded flow (the caller signs + submits the funding transaction between the two). Reads are scoped to the reader: an entity owned by anyone else answers 404, exactly like an unknown id.
| Method | Route | Purpose |
|---|---|---|
POST |
/api/v3/baas/entities/createToken |
Create a token entity against a published rule |
POST |
/api/v3/baas/entities/createAccount |
Create an account entity |
POST |
/api/v3/baas/entities/createTopic |
Create an HCS topic entity |
POST |
/api/v3/baas/entities/createAgent |
Create a smart-agent entity |
POST |
/api/v3/baas/entities/prepare-create |
Run the per-entity DKG and return the unsigned payer-funding transaction(s); payerAccountId opts a customer in as payer |
POST |
/api/v3/baas/entities/execute-create |
Finalize after the payer funds the entity |
POST |
/api/v3/baas/entities/:entityId/transfer |
Transfer from the entity |
POST |
/api/v3/baas/entities/:entityId/withdraw/prepare / .../withdraw/execute |
Two-phase withdraw (see Server-side broadcast + idempotency) |
POST |
/api/v3/baas/entities/:entityId/{mint,burn,compliance,trustline,message} |
Token and topic operations on the entity |
GET |
/api/v3/baas/entities/:entityId |
Fetch one of your entities |
GET |
/api/v3/baas/entities |
List your entities |
Async entity creation (wait: false → 202 + poll)
Section titled “Async entity creation (wait: false → 202 + poll)”A canonical DKG create runs a full ceremony (per-entity DKG → HCS rule
publication → the on-chain create), which can take long enough that a client
would rather not hold a request open — and a dropped-then-retried POST risks
creating the entity twice. The single-shot create routes (createToken,
createAccount, createTopic) therefore take an opt-in async admission:
-
Add
wait: falseand a client-suppliedidempotencyKeyto the body, and the route admits the operation to the creation journal and answers immediately with202 Accepted:{"operationId": "0.0.123456","status": "pending"} -
The ceremony then runs detached. You follow it to a terminal state by polling the operation id (the
operationIdequals the created entity’s id):Terminal window curl https://gateway.testnet.hsuite.network/api/v3/baas/operations/0.0.123456 \-H "Authorization: Bearer <session-token>"{"operationId": "0.0.123456","entityId": "0.0.123456","status": "finalized","chain": "hedera","finalized": { "transactionId": "0.0.100@...", "chainAccounts": { } }}
| Field | Type | Meaning |
|---|---|---|
status |
'pending' | 'prepared' | 'finalized' | 'aborted' |
pending while the ceremony runs; prepared when a funding draft is waiting; finalized with the entity; aborted if it was tombstoned |
prepared |
object | Present on a prepared status — the payer-funding draft to sign (payer-funded flow only) |
finalized |
object | Present on a finalized status — the outcome (transactionId, chainAccounts, receipt) |
The idempotencyKey is the retry safety net: an async retry re-signs a fresh
request, and only the key binds it back to the first admission. A retry carrying
the same key replays the first admission’s operationId and current journal
snapshot (plus any already-available result) instead of starting a second
ceremony.
Current constraints:
wait: falseis Hedera-only oncreateToken/createAccount/createTopic; other chains keep the synchronous (wait: true, the default) shape, and await: falsefor a non-Hedera chain is refused400.wait: falsewithout anidempotencyKeyis refused400.createAgentrefuseswait: false(400).prepare-createaccepts it, with anidempotencyKey, for the XRPL token and account drafts and for agents.wait: falseon the account-entity/transferroute is refused400— the operation-status contract can only represent a creation, so an admitted transfer could never be answered onGET /baas/operations/:operationId. Keep the synchronous call.
GET /api/v3/baas/operations/:operationId is owner-scoped and fail-closed:
the operation must belong to the caller’s own session app and read scope, or the
route answers the same 404 as an absent operation (never an existence oracle);
a session not bound to an app gets 403, and 503 when the journal is
unreachable.
Functions — /api/v3/baas/functions/*
Section titled “Functions — /api/v3/baas/functions/*”Serverless function lifecycle. :appId-scoped and session-authenticated; only an app-owner session may call them (see Authentication).
| Method | Route | Purpose |
|---|---|---|
POST |
/api/v3/baas/functions/:appId |
Deploy a function |
POST |
/api/v3/baas/functions/:appId/eval |
One-off eval |
POST |
/api/v3/baas/functions/:appId/:functionId/invoke |
Invoke a deployed function |
GET |
/api/v3/baas/functions/:appId/:functionId |
Function metadata |
GET |
/api/v3/baas/functions/:appId/:functionId/code |
Fetch a function’s source code + integrity hash |
GET |
/api/v3/baas/functions/:appId |
List functions for an app |
PUT |
/api/v3/baas/functions/:appId/:functionId |
Update a function |
POST |
/api/v3/baas/functions/:appId/:functionId/pause |
Pause a function: invoke answers 409 (code: STATE_CONFLICT, message: "Function is paused") on every replica until resumed. Same authorization as update; idempotent. SDK: functions.pause(functionId) |
POST |
/api/v3/baas/functions/:appId/:functionId/resume |
Resume a paused function. Same authorization as update; idempotent. SDK: functions.resume(functionId) |
DELETE |
/api/v3/baas/functions/:appId/:functionId |
Delete a function |
GET |
/api/v3/baas/functions/:appId/:functionId/logs |
Function logs |
GET |
/api/v3/baas/functions/:appId/stats |
Aggregate function stats |
Agents — /api/v3/baas/agents/*
Section titled “Agents — /api/v3/baas/agents/*”Smart-agent registration, funding, action, and lifecycle. Reads are scoped to the caller: an agent outside your session’s app and scope answers 404, the same as an unknown id.
| Method | Route | Purpose |
|---|---|---|
POST |
/api/v3/baas/agents/register |
Register a smart-agent |
GET |
/api/v3/baas/agents/:agentId |
Agent record |
GET |
/api/v3/baas/agents |
List agents |
POST |
/api/v3/baas/agents/:agentId/{fund,trade,execute,withdraw,pause,resume,revoke} |
Agent value-movement + lifecycle actions |
DELETE |
/api/v3/baas/agents/:agentId |
Delete an agent (owner only): revokes it and hides it from every read; it keeps its limits.agents slot |
PATCH |
/api/v3/baas/agents/:agentId/rules |
Amend the agent’s rule binding |
GET |
/api/v3/baas/agents/:agentId/{events,balances} |
Action audit log / balances |
GET |
/api/v3/baas/agents/:agentId/certification |
Certification snapshot — rule anchor (entityId / rulesHash / BLS group key), state-history topic, and recent action txIds |
POST |
/api/v3/baas/agents/:agentId/{approve,reject}/:opId |
Approval-workflow decisions |
Subscriptions — /api/v3/subscription/*
Section titled “Subscriptions — /api/v3/subscription/*”App tier lifecycle: request, mint, status, renew, upgrade/downgrade, usage, billing. Full table in Subscriptions client.
Faucet (testnet HSUITE) — /api/v3/faucet/*
Section titled “Faucet (testnet HSUITE) — /api/v3/faucet/*”Public bootstrap surface for XRPL wallets — no subscription or token required; wallet ownership is proven by signature. See Faucet.
| Method | Route | Purpose |
|---|---|---|
POST |
/api/v3/faucet/hsuite/challenge |
Request a single-use ownership challenge |
POST |
/api/v3/faucet/hsuite |
Dispense HST (or trustline_required / rate_limited; 503 with a reason when the faucet cannot pay) |
GET |
/api/v3/faucet/hsuite/status |
Today’s dispense status for a wallet |
Resources & metering — /api/v3/resources/*
Section titled “Resources & metering — /api/v3/resources/*”Read-only usage and quota for a smart-app. See Metering & consumption.
Every :appId route is scoped to the app’s owner. Holding a valid session is
not enough: the caller must own the subscription for the appId named in the
path, or one bearer could read another tenant’s consumption by naming their
appId. A caller who is not the owner (or an app with no subscription) gets a
404 “Subscription not found” — missing and not-owned collapse to one answer, so
the route is not an existence oracle; a request with no session at all gets 401.
“Owner” is a (wallet, chain) pair: the app’s registered developerWallet on the
chain it was created on (developerChain). The same wallet string signed in on another
chain is a different identity and gets the same 404. An app registered before
developerChain was recorded is decided on the wallet alone.
The ledger read is capped and projected. An omitted limit is 100; any limit is
clamped to 500 records per page, and a non-positive or unparseable one reads 50
(total still counts every record, so page on with offset). Each record carries only the
documented fields (_id, appId, category, operation, cost, timestamp, tier, bytesIn,
bytesOut, subscriptionNftSerial, the customer* attribution fields, and
metadata.{method,path,attribution,agentId,shadowHsuiteCost,opIdempotencyKey}); no
other field is served.
| Method | Route | Purpose | Auth |
|---|---|---|---|
GET |
/api/v3/resources/remaining/:appId |
Remaining daily quota — apiCallsRemaining / bytesRemaining (null = unlimited tier, 0 = exhausted), plus tier, nftSerial, and resetUtc |
Owner-only |
GET |
/api/v3/resources/consumption/:appId |
Current usage summary (also /history, /ledger, /breakdown, /status) |
Owner-only |
GET |
/api/v3/resources/summary |
Network resource summary — by node type only, no tenant field | Session bearer |
GET |
/api/v3/resources/nodes / /nodes/:nodeId |
Per-node resource reports (:nodeId is a validator node id, not a tenant) |
Session bearer |
Quick example
Section titled “Quick example”# 1. Request a challengecurl -X POST https://gateway.testnet.hsuite.network/auth/validator/challenge \ -H "Content-Type: application/json" \ -d '{ "chain": "xrpl", "address": "rYourAddress..." }'
# 2. Sign the returned challenge, then authenticatecurl -X POST https://gateway.testnet.hsuite.network/auth/validator/authenticate \ -H "Content-Type: application/json" \ -d '{ "chain": "xrpl", "address": "rYourAddress...", "publicKey": "ED...", "signature": "...", "challenge": "..." }'
# 3. Use the tokencurl https://gateway.testnet.hsuite.network/auth/validator/session \ -H "Authorization: Bearer <token>"Generated reference (Compodoc)
Section titled “Generated reference (Compodoc)”The hand-written contract on this page is the public reference. HSuite engineering also generates a source-level reference of every module, controller, service and DTO with Compodoc; it is internal and is not published.
Swagger / OpenAPI UI (off in production)
Section titled “Swagger / OpenAPI UI (off in production)”There is no browsable Swagger/OpenAPI UI on a live testnet or mainnet cluster. Each service can render one for development, but it is off by default and a production build never serves it, even when it is switched on.