Skip to content

REST API

Base path: <artifact-api-prefix>/app-chain, by default /api/v1/app-chain.

The prefix is fixed into each JVM, native, or container artifact at build time with -PyanoApiPrefix=<path>. It is not editable launch configuration, and changing it requires a rebuild.

Every chain endpoint below is also available chain-scoped:

/api/v1/app-chain/chains/{chainId}/...

The chain-less form keeps working while exactly one chain is configured. With several chains it returns 400 (ambiguous), and 503 when no chain is enabled.

Prefer chains/{chainId} in new integrations. Swagger UI at /q/swagger-ui/ documents the chain-scoped surface only — the chain-less aliases are deliberately hidden from the OpenAPI document, because on a multi-chain node they can only answer 400.

When API-key authentication is enabled, every request needs X-API-Key.

Method and pathPurpose
GET /chainsHosted chains: [{chainId, tipHeight, stateRoot}].
GET /statusRole, tip height, state root, pool size, peer connectivity, counters, anchor and sink progress.
GET /tip{chainId, height, stateRoot} of the last finalized block.
Method and pathPurpose
POST /messagesSubmit. Body {"topic":"...","body":"<text>"} or {"topic":"...","bodyHex":"<hex>"}. Returns 202 with the content-derived messageId.
GET /messages?limit=100&topic=...Recently accepted messages (local and peer), with sender, sequence, body hex, and source.
GET /messages/{messageIdHex}One finalized message: position (height, index) plus full content.
GET /messages/by-topic/{topic}?fromHeight=&limit=Finalized message refs on a topic, ascending.
GET /messages/by-sender/{senderHex}?fromHeight=&limit=Finalized message refs from a member key, ascending.
Method and pathPurpose
GET /blocks/{height}Hashes, roots, proposer, certificate signature count, full message list.
GET /blocks?from=&limit=Paged block summaries, ascending. Defaults to a window ending at the tip.
GET /state/proof/{keyHex}MPF inclusion proof for a state key against the committed root. For ordered-log the key is the message id, and the response includes the value and finalizedAtHeight.
POST /proof-subjects/{subjectId}/proofTyped proof in application language. See State and proofs.
GET /evidence/{messageIdHex}A portable, offline-verifiable evidence bundle for a finalized message.
Method and pathPurpose
GET /stream?fromHeight=&topic=SSE stream of finalized messages: replay from fromHeight, then live.

These are privileged. Protect them with an API key.

Method and pathPurpose
POST /snapshotAtomic ledger snapshot for fast member onboarding. Body {"path":"<fresh dir>"}.
POST /admin/pause, POST /admin/resumePause or resume local submissions.
POST /admin/drain-poolDrop all pending, unfinalized messages.
POST /admin/force-anchorAnchor the current tip now.
GET /admin/membersEffective member set and threshold.
POST /admin/members/add, .../removeStage a member key in or out. Body {"publicKey":"..."}.
POST /admin/members/resetDrop the persisted member override and return to the configured list.
POST /admin/thresholdSet the finality threshold. Body {"threshold": N}.

Plugins contribute their own bounded, read-only routes below /api/v1/plugins/<bundle-id>/.

The node signs with its own member key. The REST caller is trusted local input — the same model as a wallet talking to its own node. This is why the API must not be exposed to untrusted callers without authentication.

The body is opaque. Use body for UTF-8 text or bodyHex for arbitrary bytes. The framework never parses it; only the state machine does.

LimitDefault
max-message-bytes64 KB
default-ttl-seconds600 — an unfinalized message expires out of the pool
pool.max-messages10,000

Topics starting with ~ are reserved for consensus and system traffic.

When the pending pool is full, POST /messages returns 429 and the message is neither stored nor relayed. Back off and retry.

Inbound gossip dropped by a full pool is counted in GET /status under drops.pool_full. That is not an error — the sender’s own node already holds the message.

Every envelope carries a per-sender sequence number. A message whose seq is at or below the sender’s last finalized seq is a replay and is rejected at admission on every ledger node, counted as drops.stale_seq.

Gaps are allowed and meaningless — seqs are wall-clock seeded, so a restart never reuses one. The seq does not define ordering; the sequencer does.

yano.app-chain.message.enforce-sender-seq: true

With that flag the rule becomes consensus-visible: followers reject any block whose per-sender seqs are not strictly increasing above the finalized floor. It defaults to off for compatibility, and — like the state-machine id — all members must agree on it.

Prefer a typed client over raw HTTP where one exists:

ArtifactWhat it adds
yano-x-clientREST, SSE, and client-side proof verification.
yano-x-composite-clientGoverned-profile finality, one-root MPF, epoch-chain, and authorization-policy verification.
yano-x-spring-boot-starterSpring Boot auto-configuration for the client SDK.
yano-appchain-core-testkitJUnit 5 @AppChainCluster embedded clusters.

The exhaustive API, configuration, security, and operations reference is section 4 of the app-chain user guide.