Docs/SDKs & toolsv0.0.1-preview
SDKs & tools overview
Five client SDKs, a REST gateway, and a command-line client — one wire protocol, one idempotency contract, one set of status codes.
TulaDB clients speak a binary TCP protocol directly to the engine — writes on port 7700, reads on the dedicated query port 7800. Every SDK implements the same contract (wire version 4), verified byte-for-byte against shared cross-SDK test vectors and checked against the engine’s compiled struct sizes.
Choose your integration path
| Path | Best for | Maturity |
|---|---|---|
| Python SDK | Reference implementation — richest client surface | Most complete |
| Java SDK | JVM services; feature parity with Python | Complete |
| Go SDK | Lean, dependency-free raw client | Core client only |
| Rust SDK | Zero-dependency raw client, native u128 account ids |
Core client only |
| C SDK | Embedding in native systems; starter surface | Starter |
| REST gateway | Any language over HTTP + JSON, one OpenAPI 3.1 contract | Preview |
All SDKs are versioned 0.0.1-preview together and speak wire version 4 only; the engine refuses any other version, so an SDK built for an earlier development wire cannot talk to this release. None of them is published to a public package registry yet: each is a file in the sdk download, installed from disk (how, per language). Wire format, REST schema, and configuration keys may change before 1.0 — pin to a tag.
The common shape
Whatever the language, the client model is the same:
LedgerClient— one client owns one TCP connection. Point it at7700to write; open a second client against7800to keep read load off the write listener.- Accounts are addressed by key. An account is named by
(ledger_id, currency, id), so every account-addressed call — balance, history, limit updates, activation — takes an account key (AccountKeyin Python, Java, Go and Rust;ledgerd_account_key_tin C), never a bare id.list_account_balances(ledger_id, id)returns every currency balance one id holds in a ledger. - Every write is a batch on the wire. The engine accepts only the batch write messages; the single-op calls (
transfer,reserve,commit, …) send a batch of one and map the item back. A declined single-op call surfaces as the SDK’s error carrying the status (an explicit reject’s status41is the one decline that counts as success); batch calls return a status per item instead. - Leader-aware clients (Python, Java, and the C smart client) discover the cluster leader, route writes to it, and retry across a re-election. A transport failure or a status that isn’t final refreshes the leader and retries, and a final status is returned at once. The budget is the number of retries after the first attempt, default
RETRY_ATTEMPTS= 1. The backoff is the contract’s full jitter: 50 ms initial delay, 2000 ms cap, and no retry started more than 15,000 ms after the first attempt. Go and Rust deliberately ship the raw client only, so routing is yours. Each has a retry helper with the same budget and backoff (tuladb.Retry,RetryPolicy::retry) that retries retryable statuses and transport errors. See Errors and rejections. - Pooling and buffering (Python, Java) —
LedgerClientPoolshards work across leader-aware clients;BufferedLedgerClientcoalesces individual calls of every kind (transfer, reserve, commit, rollback, the tiered and by-key forms, rejects) into wire batches transparently. Go and Rust get the sameBufferedLedgerClient, over one connection, in the next SDK release (Go, Rust). - Batching — up to 1,024 transfers per batch frame (
TXN_BATCH_MAX, matching the engine’s frame limit), and up to 64 in-flight batch frames per connection (pipeline_depth). Results always return in input order.
Safe by default
Every SDK refuses to send plaintext ledger traffic to a non-loopback host. Account ids, balances, and money-moving frames don’t go over a network in the clear unless you explicitly opt in (allow_plaintext or its per-language equivalent). The supported posture for anything off-box is TLS with mutual authentication — client certificate, key, and CA, configurable in code or via TULADB_CLIENT_TLS_* environment variables. See Security.
One idempotency contract
Every write is keyed by (source_id, ledger_id, currency, txn_id). The engine derives the transfer’s permanent identity from that tuple with SHA3-256, and a duplicate submission — a retry, a redelivered queue message — returns the original result with status 1 (OK_DUPLICATE) instead of double-posting. Because of this, a failed batch or lost connection is safe to resubmit whole: the parts that landed come back as duplicates.
Every SDK exposes the same status classification, and every SDK names every status from 0 to 48 (48 is ERR_RECONFIG_NO_QUORUM):
- Accepted is exactly status
0, and duplicate is exactly status1. - Final statuses are the same set in every SDK. These verdicts are identical on every node and every retry, so resubmitting can only repeat them.
- Retryable statuses are also one shared set. The engine wrote nothing, so you retry with the same identity.
- Everything else depends on state that can change. A retry of a write that was declined answers the original decline, not
OK_DUPLICATE. See Errors and rejections.
Also in the box
tulactl— the command-line client for scripting, inspection, and cluster operations. See tulactl.- Dashboard — the operator console: ledgers, accounts, transfers, pending reservations and rejected records, with role-gated writes and an audit log of every change. See Observability.