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.

On this page
  1. Choose your integration path
  2. The common shape
  3. Safe by default
  4. One idempotency contract
  5. Also in the box

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 at 7700 to write; open a second client against 7800 to 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 (AccountKey in Python, Java, Go and Rust; ledgerd_account_key_t in 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 status 41 is 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) — LedgerClientPool shards work across leader-aware clients; BufferedLedgerClient coalesces 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 same BufferedLedgerClient, 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 status 1.
  • 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.