Skip to content

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. TrutoClient is 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() or truto.syncJobs(). Most expose list, get, create, update (PATCH), and delete.
  • Typed models. Admin API methods take and return generated classes in one.truto.sdk.models, such as TenantCreateRequest and IntegratedAccountResponse. 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, a Map<String, Object> with typed getters such as getString. Decode one into your own class with a Class<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 (null for either is fine).
  • Query parameters are a Map<String, Object>. Query is 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 TrutoException carrying 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, and 504, plus any added with retryStatusCodes. Connection errors are retried too. 409 is retried by default, unlike the retry strategy advice. Use RequestOptions.builder().noRetry().build() where a conflict is expected, such as tenants().create with an ID that may exist.
  • Waits. A 429 waits for the Retry-After header, or defaultRetryAfter (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 TrutoTimeoutException and 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}

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 run

Webhooks

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 with status.value().
  • Union types, such as IntegrationPagination and Log, wrap the raw JSON and decode on demand: pagination.asIntegrationPagePagination().