Java SDK
truto-java-sdk is a Java client for the Truto API. It covers the Unified, Proxy, and Custom APIs, MCP, and most of the Admin API in the Truto API Reference. It doesn't wrap notification destinations, SSO connections, SCIM, or group mappings yet; call those with truto.http(). Admin API responses and most request bodies are typed classes generated from Truto's OpenAPI spec.
Package: central.sonatype.com/artifact/one.truto/truto-java-sdk. Source: github.com/trutohq/truto-java-sdk. The methods on this page are in version 0.1.0.
Requirements
Java 11+. The only runtime dependency is Jackson (jackson-databind); HTTP goes through java.net.http.
Installation
Maven:
<dependency>
<groupId>one.truto</groupId>
<artifactId>truto-java-sdk</artifactId>
<version>0.1.0</version>
</dependency>Gradle:
implementation("one.truto:truto-java-sdk:0.1.0")Quick start
Create an API token, then:
import one.truto.sdk.Query;
import one.truto.sdk.TrutoClient;
import one.truto.sdk.models.EnvironmentIntegrationResponse;
import one.truto.sdk.models.IntegratedAccountResponse;
import one.truto.sdk.models.TrutoRecord;
import java.util.List;
public class QuickStart {
public static void main(String[] args) {
TrutoClient truto = TrutoClient.builder()
.token(System.getenv("TRUTO_API_TOKEN"))
.build();
// Installed integrations in the environment
for (EnvironmentIntegrationResponse integration : truto.environmentIntegrations().list()) {
System.out.println(integration.getId());
}
// Integrated accounts for a tenant
List<IntegratedAccountResponse> accounts = truto.integratedAccounts()
.list(Query.of("tenant_id", "acme-1"))
.toList();
// A Unified API call
for (TrutoRecord contact : truto.unifiedApi().list("crm", "contacts", accounts.get(0).getId())) {
System.out.println(contact.getString("first_name"));
}
}
}How the client works
- One client, many threads.
TrutoClientis immutable and thread-safe. Build it once and share it. - Resources. Each Admin API resource is an accessor on the client, such as
truto.tenants()ortruto.syncJobs(). Most exposelist,get,create,update(PATCH), anddelete. - Typed models. Admin API methods take and return generated classes in
one.truto.sdk.models, such asTenantCreateRequestandIntegratedAccountResponse. Request classes have fluent setters:new TenantCreateRequest().id("acme-1").name("Acme"). - Records. Unified, Proxy, Custom, and MCP results depend on the integration, so they come back as
TrutoRecord, aMap<String, Object>with typed getters such asgetString. Decode one into your own class with aClass<T>overload (see Decode into your own types). - Short and long forms. Every method has a short form, and a long form that also takes a query map and
RequestOptions(nullfor either is fine). - Query parameters are a
Map<String, Object>.Queryis a small builder that is itself a map:Query.of("tenant_id", "acme-1").with("limit", 50). Nested maps and lists are encoded the way the API expects (filter[status]=open,ids[0]=a). - Errors. Every failure is an unchecked
TrutoExceptioncarrying the status, the response body, and the headers (see Errors). - Base URL. It defaults to
https://api.truto.one. Call.baseUrl(...)on the builder to use a different one.
truto.integratedAccounts().get("ia-id");
truto.integratedAccounts().get("ia-id", Query.of("expand", "integration"), RequestOptions.builder().maxRetries(0).build());Client options
TrutoClient truto = TrutoClient.builder()
.token(System.getenv("TRUTO_API_TOKEN")) // required
.baseUrl("https://api.truto.one") // default
.timeout(Duration.ofSeconds(30)) // default, per attempt
.maxRetries(3) // default
.defaultRetryAfter(Duration.ofSeconds(10)) // 429 without Retry-After
.onRetry((retry, status) -> System.err.println("retry " + retry + " after " + status))
.onRateLimited((seconds, retries) -> System.err.println("rate limited for " + seconds + "s"))
.build();.httpClient(...) takes your own java.net.http.HttpClient, for a proxy or a custom executor.
Errors
try {
truto.integratedAccounts().get("missing-id");
} catch (TrutoTimeoutException e) { // client-side timeout, never retried
} catch (TrutoTransportException e) { // no response (connection refused, DNS, ...)
} catch (TrutoDecodeException e) { // a 2xx body that did not fit the type
} catch (TrutoException e) {
e.getStatusCode(); // 404
e.getStatusText();
e.getBody(); // JsonNode for JSON bodies, String otherwise
e.getJsonBody();
e.getHeaders();
e.isNotFound(); // also isRateLimited(), isUnauthorized()
}The error body is Truto's JSON response, so its message and truto_error_insight fields are available from getJsonBody(). Async methods complete exceptionally with the same exceptions, wrapped in CompletionException by join().
Retries and timeouts
The Java SDK retries failed requests for you. Keep the rate limits in mind: 100 requests per second per API token, and 50 requests per 10 seconds per integrated account.
- Retried statuses.
408,409,425,429,502,503, and504, plus any added withretryStatusCodes. Connection errors are retried too.409is retried by default, unlike the retry strategy advice. UseRequestOptions.builder().noRetry().build()where a conflict is expected, such astenants().createwith an ID that may exist. - Waits. A
429waits for theRetry-Afterheader, ordefaultRetryAfter(10 seconds) when it is missing. Other retryable statuses back off with full jitter: a random wait up to 500 ms, with the ceiling doubling each time, capped at 10 seconds. - Retries. Up to 3 by default.
- Timeout. 30 seconds per attempt by default. A timed-out attempt throws
TrutoTimeoutExceptionand is not retried.
Override any of these per request with RequestOptions:
RequestOptions opts = RequestOptions.builder()
.timeout(Duration.ofSeconds(5))
.maxRetries(1)
.retryStatusCodes(500) // in addition to the defaults
.header("X-Request-Id", "abc")
.build();
IntegratedAccountResponse account = truto.integratedAccounts().get("ia-id", null, opts);
RequestOptions once = RequestOptions.builder().noRetry().build();Retry a create or other write only when sending it twice is harmless: a 502 or 504 can arrive after the provider has already applied the change. Pass RequestOptions.builder().noRetry().build() on writes that aren't safe to repeat. Cancelling an async future stops any further retries.
Pagination
list methods return a lazy Pager<T> that follows next_cursor for you. Nothing is fetched until you use it.
Iterate it with a for loop, or stream it:
Pager<IntegratedAccountResponse> accounts = truto.integratedAccounts().list(Query.of("limit", 100));
for (IntegratedAccountResponse account : accounts) {
System.out.println(account.getId());
}
truto.integratedAccounts().list().stream()
.limit(10)
.forEach(a -> System.out.println(a.getId()));Collect every page with toList(), or take the first item with first():
List<TenantResponse> tenants = truto.tenants().list().toList();
Optional<TenantResponse> first = truto.tenants().list().first();Or fetch one page at a time:
Pager<TenantResponse> pager = truto.tenants().list(Query.of("limit", 50));
while (pager.hasNextPage()) {
Page<TenantResponse> page = pager.nextPage();
System.out.println(page.items().size() + " tenants, next cursor " + page.nextCursor());
}Your query map is copied, never modified.
Unified API
Accessible via truto.unifiedApi(). Each call takes the unified model, the resource, and the integrated account ID. See the Unified API reference for the models and resources available.
String accountId = "766cc1ee-...";
Pager<TrutoRecord> contacts = truto.unifiedApi().list("crm", "contacts", accountId, Query.of("limit", 50));
TrutoRecord contact = truto.unifiedApi().get("crm", "contacts", accountId, "123");
TrutoRecord created = truto.unifiedApi().create("crm", "contacts", accountId,
new TrutoRecord().with("first_name", "Ada"));
truto.unifiedApi().update("crm", "contacts", accountId, "123", Map.of("first_name", "Grace"));
truto.unifiedApi().delete("crm", "contacts", accountId, "123");
// Custom methods: POST /unified/{model}/{resource}/{method}
TrutoRecord results = truto.unifiedApi().customMethod("crm", "contacts", accountId, "search", Map.of("query", "acme"));| Method | Endpoint |
|---|---|
list(model, resource, accountId[, query]) |
GET /unified/{model}/{resource} |
get(model, resource, accountId, id) |
GET /unified/{model}/{resource}/{id} |
create(model, resource, accountId, body) |
POST /unified/{model}/{resource} |
update(model, resource, accountId, id, body) |
PATCH /unified/{model}/{resource}/{id} |
delete(model, resource, accountId, id) |
DELETE /unified/{model}/{resource}/{id} |
customMethod(model, resource, accountId, method, body) |
POST /unified/{model}/{resource}/{method} |
meta(model, resource, integrationName, method) |
GET /unified/{model}/{resource}/{integration_name}/meta/{method} |
modelMeta(model, integrationName) |
GET /unified/meta/{model}/{integration_name} |
meta returns an integration's mapping metadata (schema and supported parameters) for one unified method. modelMeta returns the resources and methods of a unified model that an integration supports.
UnifiedApiMetaResponse meta = truto.unifiedApi().meta("crm", "contacts", "hubspot", "list");
UnifiedMetaGetResponse crmOnHubSpot = truto.unifiedApi().modelMeta("crm", "hubspot");Async calls
getAsync, createAsync, updateAsync, deleteAsync, customMethodAsync, and listPageAsync return a CompletableFuture. They take the query and RequestOptions as their last two arguments:
truto.unifiedApi().getAsync("crm", "contacts", accountId, "123", null, null)
.thenAccept(c -> System.out.println(c.getString("first_name")))
.join();The Proxy API has the same async variants.
Decode into your own types
Pass a Class<T> to the long form of get or list, call TrutoRecord.as, or map a pager:
public class Contact {
public String id;
@JsonProperty("first_name") public String firstName;
}
Contact c = truto.unifiedApi().get("crm", "contacts", accountId, "123", null, null, Contact.class);
Pager<Contact> all = truto.unifiedApi().list("crm", "contacts", accountId, null, null, Contact.class);
Contact again = contact.as(Contact.class);Proxy API
Accessible via truto.proxyApi(). Calls go to the integration's own API through Truto, with resource names as defined on the integration.
String accountId = "766cc1ee-...";
Pager<TrutoRecord> tickets = truto.proxyApi().list("tickets", accountId);
TrutoRecord ticket = truto.proxyApi().get("tickets", accountId, "123");
truto.proxyApi().create("tickets", accountId, Map.of("subject", "Help"));
truto.proxyApi().update("tickets", accountId, "123", Map.of("status", "closed"));
truto.proxyApi().delete("tickets", accountId, "123");
truto.proxyApi().customMethod("tickets", accountId, "merge", Map.of("ids", List.of("1", "2")));| Method | Endpoint |
|---|---|
list(resource, accountId[, query]) |
GET /proxy/{resource} |
get(resource, accountId, id) |
GET /proxy/{resource}/{id} |
create(resource, accountId, body) |
POST /proxy/{resource} |
update(resource, accountId, id, body) |
PATCH /proxy/{resource}/{id} |
delete(resource, accountId, id) |
DELETE /proxy/{resource}/{id} |
customMethod(resource, accountId, method, body) |
POST /proxy/{resource}/{method} |
Custom API
Accessible via truto.customApi(). Sends an arbitrary request to the integration's API through Truto, at /custom/{path}.
TrutoRecord result = truto.customApi().get("my/custom/path", accountId);
truto.customApi().post("my/custom/path", accountId, Map.of("key", "value"));
// Non-JSON responses, such as files: use the raw variant
RawResponse file = truto.customApi().requestRaw("GET", "files/42/download", accountId, null, null, null);
byte[] bytes = file.body();| Method | Endpoint |
|---|---|
get(path, accountId) |
GET /custom/{path} |
post(path, accountId, body) |
POST /custom/{path} |
put(path, accountId, body) |
PUT /custom/{path} |
patch(path, accountId, body) |
PATCH /custom/{path} |
delete(path, accountId) |
DELETE /custom/{path} |
requestRaw(method, path, accountId, body, query, options) |
Any method; returns the raw bytes |
For an endpoint that returns a JSON array, use customApi().request(method, path, accountId, body, query, options, JsonNode.class).
MCP
Accessible via truto.mcp(). Talks to an integrated account's MCP server, using the token from Create MCP server.
String mcpToken = "your-mcp-token";
truto.mcp().initialize(mcpToken, "my-client", "1.0.0");
TrutoRecord tools = truto.mcp().listTools(mcpToken);
TrutoRecord result = truto.mcp().callTool(mcpToken, "list_tickets", Map.of("status", "open"));
// Any other JSON-RPC message
truto.mcp().call(mcpToken, Map.of("jsonrpc", "2.0", "method", "tools/list", "id", 3));| Method | Description |
|---|---|
initialize(token, clientName, clientVersion) |
Sends initialize |
listTools(token) |
Sends tools/list |
callTool(token, name, arguments) |
Sends tools/call |
call(token, payload) |
Sends a raw JSON-RPC payload to POST /mcp/{token} |
Integrated accounts
Accessible via truto.integratedAccounts(). API reference.
Pager<IntegratedAccountResponse> accounts = truto.integratedAccounts().list(Query.of("tenant_id", "acme-1"));
IntegratedAccountResponse account = truto.integratedAccounts().get("ia-id");
truto.integratedAccounts().validate("ia-id");
truto.integratedAccounts().bulkDelete(Map.of("tenant_id", "acme-1"));
CapabilitiesResponse capabilities = truto.integratedAccounts().capabilities("ia-id");| Method | Endpoint |
|---|---|
list([query]) |
GET /integrated-account |
get(id) |
GET /integrated-account/{id} |
create(body) |
POST /integrated-account (import an account with existing credentials) |
update(id, body) |
PATCH /integrated-account/{id} |
delete(id) |
DELETE /integrated-account/{id} |
updateCredentials(id, body) |
PATCH /integrated-account/{id}/credentials |
bulkDelete(body) |
POST /integrated-account/bulk-delete (by ids or tenant_id) |
createToken(id) |
POST /integrated-account/token |
refreshCredentials(id) |
POST /integrated-account/refresh-credentials |
refreshMappingCache(body) |
POST /integrated-account/refresh-mapping-cache |
createFormToken(id) |
POST /integrated-account/form-token |
runPostInstallActions(id) |
POST /integrated-account/run-post-install-actions |
validate(id) |
POST /integrated-account/{id}/validate |
capabilities(id) |
GET /integrated-account/{id}/capabilities |
tools(id[, methods, tags, query, options]) |
GET /integrated-account/{id}/tools (deprecated; use capabilities) |
MCP servers
Each integrated account can have MCP servers. API reference.
truto.integratedAccounts().createMcpServer("ia-id", new IntegratedAccountMcpCreateRequest()
.name("Support agent")
.config(new IntegratedAccountMcpCreateRequestConfig().methods(List.of("list", "get"))));
List<MCP> servers = truto.integratedAccounts().listMcpServers("ia-id").toList();| Method | Endpoint |
|---|---|
listMcpServers(iaId) (a pager) |
GET /integrated-account/{ia_id}/mcp |
createMcpServer(iaId, body) |
POST /integrated-account/{ia_id}/mcp |
getMcpServer(iaId, id) |
GET /integrated-account/{ia_id}/mcp/{id} |
updateMcpServer(iaId, id, body) |
PATCH /integrated-account/{ia_id}/mcp/{id} |
deleteMcpServer(iaId, id) |
DELETE /integrated-account/{ia_id}/mcp/{id} |
Link tokens
Accessible via truto.linkTokens(). API reference.
LinkToken token = truto.linkTokens().create(new LinkTokenRequest().tenantId("acme-1"));
System.out.println(token.getLinkToken());| Method | Endpoint |
|---|---|
create(body) |
POST /link-token |
Session tokens
Short-lived tokens scoped to one tenant, used by the connections widget. Accessible via truto.sessionTokens().
SessionTokenResponse session = truto.sessionTokens().create(new SessionTokenRequest().tenantId("acme-1"));| Method | Endpoint |
|---|---|
create(body) |
POST /session-token |
revoke(body) |
DELETE /session-token |
renew() |
POST /session-token/renew |
me() |
GET /session-token/me |
Tenants
Accessible via truto.tenants(). API reference.
truto.tenants().create(new TenantCreateRequest().id("acme-1").name("Acme"));
truto.tenants().update("acme-1", new TenantUpdateRequest().name("Acme Inc"));| Method | Endpoint |
|---|---|
list, get, create, update, delete |
/tenant, /tenant/{id} |
bulkCreate(body) |
POST /tenant/bulk |
Integrations
Accessible via truto.integrations(). API reference.
IntegrationResponse integration = truto.integrations().get("integration-id");
CapabilitiesResponse capabilities = truto.integrations().capabilities("hubspot"); // ID or name
truto.integrations().install("integration-id");
truto.integrations().uninstall("integration-id");| Method | Endpoint |
|---|---|
list, get, create, update, delete |
/integration, /integration/{id} |
capabilities(idOrName) |
GET /integration/{id}/capabilities |
tools(idOrName) |
GET /integration/{id}/tools (deprecated; use capabilities) |
unifiedApis(idOrName) |
GET /integration/{id}/unified-apis (deprecated; use capabilities) |
install(integrationId[, isEnabled, query, options]) |
POST /environment-integration |
uninstall(integrationId) |
Finds the integration's environment integration and deletes it |
Environment integrations
Integrations installed in your environment. Accessible via truto.environmentIntegrations(). API reference.
| Method | Endpoint |
|---|---|
list([query]) |
GET /environment-integration |
get(id) |
GET /environment-integration/{id} |
create(body) |
POST /environment-integration |
update(id, body) |
PATCH /environment-integration/{id} |
delete(id) |
DELETE /environment-integration/{id} |
Unified models
Accessible via truto.unifiedModels(). API reference.
| Method | Endpoint |
|---|---|
list, get, create, update, delete |
/unified-model, /unified-model/{id} |
install(unifiedModelId) |
POST /environment-unified-model |
uninstall(unifiedModelId) |
Finds the model's environment unified model and deletes it |
Environment unified models
Unified models installed in your environment. Accessible via truto.environmentUnifiedModels(). API reference.
| Method | Endpoint |
|---|---|
list, get, create, update, delete |
/environment-unified-model, /environment-unified-model/{id} |
Unified model mappings
How a unified model's resource methods map to an integration. Both resources expose list, get, create, update, and delete.
| Accessor | Endpoint | Reference |
|---|---|---|
truto.unifiedModelResourceMethods() |
/unified-model-resource-method |
Unified model mappings |
truto.environmentUnifiedModelResourceMethods() |
/environment-unified-model-resource-method |
Environment unified model mappings |
for (EnvironmentUnifiedModelResourceMethodRowResponse override : truto.environmentUnifiedModelResourceMethods()
.list(Query.of("environment_unified_model_id", "..."))) {
System.out.println(override.getId());
}Sync jobs
Each resource below exposes list, get, create, update, and delete.
| Accessor | Endpoint | Extra methods | Reference |
|---|---|---|---|
truto.syncJobs() |
/sync-job |
Sync jobs | |
truto.syncJobRuns() |
/sync-job-run |
Sync job runs | |
truto.syncJobCronTriggers() |
/sync-job-cron-trigger |
schedule(id): POST /sync-job-cron-trigger/{id}/schedule |
Sync job cron triggers |
truto.syncJobTemplates() |
/sync-job-template |
Sync job templates | |
truto.syncJobRunStates() |
/sync-job-run-state |
Sync job run state |
SyncJobRunResponse run = truto.syncJobRuns().create(new SyncJobRunCreateRequest()
.syncJobId("sync-job-id")
.integratedAccountId("766cc1ee-..."));
truto.syncJobRuns().delete(run.getId()); // stops the runWebhooks
Accessible via truto.webhooks(). API reference.
WebhookCreateResponse webhook = truto.webhooks().create(new WebhookCreateRequest().targetUrl("https://example.com/truto"));
truto.webhooks().test(new WebhookTestRequest().id(webhook.getId()));| Method | Endpoint |
|---|---|
list, get, create, update, delete |
/webhook, /webhook/{id} |
test(body) |
POST /webhook/test |
Workflows
Accessible via truto.workflows() and truto.workflowRuns(). API reference: Workflows, Workflow runs.
for (WorkflowRunResponse run : truto.workflowRuns().list(Query.of("workflow_id", "workflow-id"))) {
System.out.println(run.getStatus());
}| Accessor | Methods | Endpoint |
|---|---|---|
truto.workflows() |
list, get, create, update, delete |
/workflow, /workflow/{id} |
truto.workflowRuns() |
list, get, create, update, delete |
/workflow-run, /workflow-run/{id} |
Datastores
Accessible via truto.datastores(). API reference.
| Method | Endpoint |
|---|---|
list, get, create, update, delete |
/datastore, /datastore/{id} |
test(id, method, body) |
POST /datastore/{id}/test/{method} |
Static gates
Accessible via truto.staticGates(). API reference.
| Method | Endpoint |
|---|---|
list, get, create, update, delete |
/static-gate, /static-gate/{id} |
Observability destinations
Accessible via truto.observabilityDestinations(). API reference.
truto.observabilityDestinations().test("observability-destination-id", new ObservabilityDestinationTestRequest());| Method | Endpoint |
|---|---|
list, get, create, update, delete |
/observability-destination, /observability-destination/{id} |
test(id, body) |
POST /observability-destination/{id}/test |
Documentation
Accessible via truto.documentation(). API reference.
| Method | Endpoint |
|---|---|
list, get, create, update, delete |
/documentation, /documentation/{id} |
Batch requests
Accessible via truto.batchRequests(). API reference.
| Method | Endpoint |
|---|---|
create(body) |
POST /batch-request |
Analytics
Accessible via truto.analytics(). API reference. Pass query parameters to the long form of each method.
AnalyticsDashboardResponse dashboard = truto.analytics().dashboard(Query.of("range", "7d"), null);
AnalyticsTimeseriesResponse series = truto.analytics().timeseries(Query.of("environment_id", "environment-id")
.with("metric", "api_calls")
.with("from", "2026-10-01")
.with("to", "2026-10-08"), null);| Method | Endpoint |
|---|---|
status(), status(query, options) |
GET /analytics/status |
metrics(), metrics(query, options) |
GET /analytics/metrics |
dashboard(), dashboard(query, options) |
GET /analytics/dashboard |
timeseries(), timeseries(query, options) |
GET /analytics/timeseries |
breakdown(), breakdown(query, options) |
GET /analytics/breakdown |
Environments, API tokens, categories, and logs
| Accessor | Methods | Endpoint | Reference |
|---|---|---|---|
truto.environments() |
list, get, update |
/environment |
Environments |
truto.apiTokens() |
list, get |
/api-token |
API tokens |
truto.categories() |
list |
/category |
Categories |
truto.logs() |
list |
/log |
Logs |
truto.logs().list(...) needs a log_type query parameter: unified_proxy_api, rapid_bridge, webhook, mcp, or sync_job_cron_trigger.
Pager<Log> logs = truto.logs().list(Query.of("log_type", "unified_proxy_api"));truto.ping() calls GET /ping to check that your token works.
Daemons
These use TrutoRecord instead of typed models. See the daemon guide.
| Accessor | Methods | Endpoint | Reference |
|---|---|---|---|
truto.daemons() |
list, get, create, update, delete |
/daemon, /daemon/{id} |
Daemons |
truto.daemonJobs() |
list, get, create, update, delete |
/daemon-job, /daemon-job/{id} |
Daemon jobs |
truto.daemonJobRuns() |
list, get, create, update, delete |
/daemon-job-run, /daemon-job-run/{id} |
Daemon job runs |
truto.daemonJobCronTriggers() |
list, get, create, update, delete, schedule |
/daemon-job-cron-trigger, /daemon-job-cron-trigger/{id} |
Daemon job cron triggers |
Other resources
These resources are in the SDK but not in the public API reference. They use TrutoRecord instead of typed models.
| Accessor | Methods | Endpoint |
|---|---|---|
truto.users() |
list, get |
/user, /user/{id} |
truto.teams() |
list, get, update |
/team, /team/{id} |
truto.sandboxIntegratedAccounts() |
list, get, create, delete |
/sandbox-integrated-account, /sandbox-integrated-account/{id} |
truto.alarms() |
list, get, create, update, delete |
/alarm, /alarm/{id} |
truto.integratedAccountWebhooks() |
process(integratedAccountId, body) |
POST /integrated-account-webhook/{id} |
truto.environmentIntegrationWebhooks() |
process(environmentIntegrationId, body) |
POST /environment-integration-webhook/{id} |
Calling any endpoint
For an endpoint the SDK doesn't wrap yet, truto.http() sends a request with the same auth, retries, and error handling:
TrutoRecord r = truto.http().request("GET", "some/new-endpoint", Query.of("a", 1), null, null, TrutoRecord.class);Typed models
Admin API types in one.truto.sdk.models, such as IntegratedAccountResponse, SyncJobCreateRequest, and WebhookSchema, are generated from Truto's OpenAPI spec.
- Lenient decoding. Unknown fields are ignored and missing fields stay
null, so new API fields don't break decoding. - Open enums. An enum value the SDK doesn't know yet is kept: check
status.isKnown()and read the raw string withstatus.value(). - Union types, such as
IntegrationPaginationandLog, wrap the raw JSON and decode on demand:pagination.asIntegrationPagePagination().