trstctl /docs GitHub ↗

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 optional X-Tenant-ID hint for header/dev auth).
  • Idempotency — an Idempotency-Key on 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.Problem in Go, TrstctlProblem in TypeScript, ProblemError in Python, ProblemException in Java) carrying status/title/detail/type/instance and any extension members.
  • Retries with backoff429/502/503/504 are retried with exponential backoff that honors a Retry-After header when the server sends one.
  • Cursor pagination — list endpoints return { items, next_cursor }; the SDK exposes iterators that follow next_cursor so 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.

Rendered live from github.com/ctlplne/trstctl — found a mistake? edit this page.