Skip to content

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.

You reach a cluster through its gateway. On testnet the canonical entrypoint is:

https://gateway.testnet.hsuite.network

The 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

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:

Terminal window
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.

The API uses Web3 challenge→verify auth:

  1. Request a challenge.
  2. Sign it with your wallet.
  3. Exchange the signature for a session token.
  4. 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.

The routes below are the ones reachable through the public gateway.

Method Route Auth
POST /auth/validator/challenge Public
POST /auth/validator/authenticate Public
GET /auth/validator/session Bearer token
GET /auth/validator/health Public

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.

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 broken 200.
  • 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 idempotencyKey each 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

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

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

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:

  1. Add wait: false and a client-supplied idempotencyKey to the body, and the route admits the operation to the creation journal and answers immediately with 202 Accepted:

    {
    "operationId": "0.0.123456",
    "status": "pending"
    }
  2. The ceremony then runs detached. You follow it to a terminal state by polling the operation id (the operationId equals 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: false is Hedera-only on createToken / createAccount / createTopic; other chains keep the synchronous (wait: true, the default) shape, and a wait: false for a non-Hedera chain is refused 400.
  • wait: false without an idempotencyKey is refused 400.
  • createAgent refuses wait: false (400). prepare-create accepts it, with an idempotencyKey, for the XRPL token and account drafts and for agents.
  • wait: false on the account-entity /transfer route is refused 400 — the operation-status contract can only represent a creation, so an admitted transfer could never be answered on GET /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.

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

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

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
Terminal window
# 1. Request a challenge
curl -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 authenticate
curl -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 token
curl https://gateway.testnet.hsuite.network/auth/validator/session \
-H "Authorization: Bearer <token>"

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.

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.