Client SDKs
trstctl ships supported client SDKs for Go, TypeScript, Python, and Java, plus blessed generator configs, so integrators do not have to hand-roll a client, retry logic, idempotency, pagination, or error mapping. The SDKs are generated / blessed against the served OpenAPI 3.1 contract and are pinned to it so they cannot silently drift from the API.
Code: clients/sdk/ (go/, typescript/, python/, java/, and the pinned openapi.json).
Regenerate: make sdk. Served contract + library.
Why these exist
The control plane already publishes its full OpenAPI 3.1 document (at
/api/v1/openapi.json) and a CLI at parity with the API. The SDKs add the next
layer every language team would otherwise re-implement:
- Auth — an
Authorization: Bearer <token>header on every call (and an optionalX-Tenant-IDhint for header/dev auth). - Idempotency — an
Idempotency-Keyon every mutation, auto-generated when you do not supply one and held stable across automatic retries, so a retried create is exactly-once on the server (a retry never applies the change twice). - problem+json (RFC 7807) — non-2xx responses parse into a typed error
(
*trstctl.Problemin Go,TrstctlProblemin TypeScript,ProblemErrorin Python,ProblemExceptionin Java) carryingstatus/title/detail/type/instanceand any extension members. - Retries with backoff —
429/502/503/504are retried with exponential backoff that honors aRetry-Afterheader when the server sends one. - Cursor pagination — list endpoints return
{ items, next_cursor }; the SDK exposes iterators that follownext_cursorso you never juggle cursors by hand.
Pinned to the served contract
The published spec lives at clients/sdk/openapi.json. A CI check fails the build
if that file drifts from the spec the running server actually serves, and a second
check pins that served spec to the live route table. So a backend field
add/rename/remove turns the build red until the SDKs are regenerated with
make sdk — the SDKs cannot desync from the API undetected.
Generation
# Re-pin clients/sdk/openapi.json to the served spec and regenerate every SDK.
make sdk
# CI guard: fail if the SDKs are out of sync with the served contract.
make sdk-check
# Build + test the Go, TypeScript, Python, and Java SDKs.
make sdk-test
Under the hood scripts/gen-sdk.sh copies the served golden to
clients/sdk/openapi.json, runs openapi-typescript for the TypeScript types,
runs the Python TypedDict generator, generates the Java OpenAPI schema index, and
(opt-in) oapi-codegen for the full Go model set.
Go
The Go SDK is its own module (trstctl.com/sdk/go) that imports nothing
outside the standard library, so adding it never drags the control plane's
dependency graph into your build.
import (
"context"
"log"
trstctl "trstctl.com/sdk/go/trstctl"
)
func main() {
// Auth: the token is sent as Authorization: Bearer on every call.
client := trstctl.New("https://localhost:8443", "trst_...")
ctx := context.Background()
// Getting-started flow in one call: create owner -> create identity ->
// transition to "issued". Each step carries its own Idempotency-Key, so a
// retry never applies the change twice.
ident, err := client.IssueFirstCertificate(ctx, "payments")
if err != nil {
// problem+json errors surface as a typed *trstctl.Problem.
if prob, ok := trstctl.AsProblem(err); ok {
log.Fatalf("issuance failed: %d %s — %s", prob.HTTPStatus, prob.Title, prob.Detail)
}
log.Fatal(err)
}
log.Printf("issued identity %s (status %s)", ident.ID, ident.Status)
// Cursor pagination: the iterator follows next_cursor across pages.
it := client.Certificates(trstctl.CertificateListOptions{ListOptions: trstctl.ListOptions{Limit: 50}})
for it.Next(ctx) {
cert := it.Value()
log.Printf("certificate %s: %s", cert.ID, cert.Subject)
}
if err := it.Err(); err != nil {
log.Fatal(err)
}
}
Idempotency, retries (429/5xx with Retry-After), and the X-Tenant-ID
hint are configured on the client:
client := trstctl.New("https://localhost:8443", "trst_...",
trstctl.WithTenant("11111111-1111-1111-1111-111111111111"),
trstctl.WithRetry(trstctl.RetryPolicy{MaxAttempts: 4, BaseDelay: 200 * time.Millisecond, MaxDelay: 5 * time.Second}),
)
// Supply your own stable Idempotency-Key when you want a retry across process
// restarts to remain exactly-once:
owner, err := client.CreateOwnerKeyed(ctx, trstctl.OwnerRequest{Kind: "workload", Name: "payments"}, "my-stable-key")
TypeScript
The TypeScript SDK's resource types are generated by openapi-typescript from
the pinned spec; a small dependency-free runtime adds the same auth, idempotency,
retry, problem+json, and pagination behavior. It requires a global fetch
(Node 18+, Deno, or a browser). The package is ready to publish as
@trstctl/sdk, includes public registry publish metadata, and carries a Node
test suite for bearer auth, optional tenant hints, core owner/identity/certificate
resources, cursor pagination, stable mutation Idempotency-Key retry behavior,
and typed problem+json errors.
import { TrstctlClient, isProblem } from "@trstctl/sdk";
const client = new TrstctlClient({
baseUrl: "https://localhost:8443",
token: "trst_...",
// tenant: "11111111-1111-1111-1111-111111111111", // optional X-Tenant-ID hint
});
try {
// create owner -> create identity -> transition to "issued", each with an
// auto-generated Idempotency-Key.
const ident = await client.issueFirstCertificate("payments");
console.log(`issued identity ${ident.id} (status ${ident.status})`);
} catch (err) {
if (isProblem(err)) {
console.error(`issuance failed: ${err.httpStatus} ${err.title} — ${err.detail}`);
} else {
throw err;
}
}
// Cursor pagination via an async generator that follows next_cursor.
for await (const cert of client.certificates({ limit: 50 })) {
console.log(`certificate ${cert.id}: ${cert.subject}`);
}
See clients/sdk/README.md and clients/sdk/typescript/README.md for the full
reference, including the exact npx openapi-typescript command and the
copy-paste helper snippet. make sdk-test runs both npm run typecheck and
npm test for the TypeScript package.
Python
The Python SDK package is trstctl-sdk. Its runtime is standard-library only,
and trstctl_sdk.types is generated from the pinned OpenAPI schemas. It is
covered by two gates: unit tests for auth/idempotency/problem handling, and a
served acceptance test that shells out to Python and performs bearer auth,
dynamic PKI issuance, and secret create/read/rotate/delete against the assembled
control-plane handler.
from trstctl_sdk import ProblemError, TrstctlClient
client = TrstctlClient.from_env()
try:
issued = client.issue_pki_secret(
"payments.service",
ttl_seconds=900,
idempotency_key="payments-pki-2026-06-25",
)
client.create_secret(
"apps/payments/api-token",
"initial-fixture-value",
idempotency_key="payments-secret-create",
)
current = client.get_secret("apps/payments/api-token")
except ProblemError as exc:
print(exc.http_status, exc.title, exc.detail)
raise
print(issued["serial"], current["version"])
Use TRSTCTL_SERVER, TRSTCTL_TOKEN, and TRSTCTL_TENANT with
TrstctlClient.from_env().
Java
The Java SDK package is com.trstctl:trstctl-sdk. Its runtime is JDK-only:
java.net.http for transport, a small in-package JSON codec, and generated
OpenApiSchemas drift metadata from the pinned OpenAPI schema names. It has the
same operational behavior as the other SDKs: bearer auth, optional X-Tenant-ID,
stable Idempotency-Key, ProblemException, Retry-After, and helpers for dynamic
PKI issuance plus secret create/read/rotate/delete.
import com.trstctl.sdk.PkiSecret;
import com.trstctl.sdk.Secret;
import com.trstctl.sdk.TrstctlClient;
TrstctlClient client = TrstctlClient.fromEnv();
PkiSecret issued = client.issuePkiSecret(
"payments.service",
900,
"payments-pki-2026-06-25"
);
Secret current = client.createSecret(
"apps/payments/api-token",
"initial-fixture-value",
"payments-secret-create"
);
System.out.println(issued.serial() + " " + current.version());
clients/sdk/java/scripts/run_tests.sh compiles the SDK and a JDK-only unit test.
CI runs the Java SDK gate in required mode, so the check fails instead of silently
skipping if the JDK disappears from the runner. The served-path acceptance test
compiles and runs a Java round-trip program against the assembled control plane when
a JDK is present.