# Java SDK

> Source: https://truto.one/docs/sdks/java/

`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](/docs/api-reference/overview/introduction). It doesn't wrap [notification destinations](/docs/api-reference/admin/notification-destinations), [SSO connections](/docs/api-reference/admin/sso-connections), [SCIM](/docs/api-reference/admin/scim), or [group mappings](/docs/api-reference/admin/group-mappings) yet; call those with [`truto.http()`](#calling-any-endpoint). 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](https://central.sonatype.com/artifact/one.truto/truto-java-sdk). Source: [github.com/trutohq/truto-java-sdk](https://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:

```xml
<dependency>
  <groupId>one.truto</groupId>
  <artifactId>truto-java-sdk</artifactId>
  <version>0.1.0</version>
</dependency>
```

Gradle:

```kotlin
implementation("one.truto:truto-java-sdk:0.1.0")
```

## Quick start

Create an [API token](/docs/guides/api-tokens/creating-api-tokens), then:

```java
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](#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](#errors)).
- **Base URL.** It defaults to `https://api.truto.one`. Call `.baseUrl(...)` on the builder to use a different one.

```java
truto.integratedAccounts().get("ia-id");
truto.integratedAccounts().get("ia-id", Query.of("expand", "integration"), RequestOptions.builder().maxRetries(0).build());
```

### Client options

```java
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

```java
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](/docs/api-reference/overview/errors#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](/docs/api-reference/overview/errors#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`:

```java
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:

```java
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()`:

```java
List<TenantResponse> tenants = truto.tenants().list().toList();
Optional<TenantResponse> first = truto.tenants().list().first();
```

Or fetch one page at a time:

```java
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](/docs/api-reference/overview/introduction) for the models and resources available.

```java
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.

```java
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:

```java
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:

```java
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.

```java
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}`.

```java
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](#mcp-servers).

```java
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](/docs/api-reference/admin/integrated-accounts).

```java
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](/docs/api-reference/admin/mcp).

```java
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](/docs/api-reference/admin/link-token).

```java
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](/docs/guides/link-sdk/connections-widget). Accessible via `truto.sessionTokens()`.

```java
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](/docs/api-reference/admin/tenants).

```java
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](/docs/api-reference/admin/integrations).

```java
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](/docs/api-reference/admin/installed-integrations-environment-integrations).

| 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](/docs/api-reference/admin/unified-models).

| 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](/docs/api-reference/admin/installed-unified-models-environment-unified-models).

| 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](/docs/api-reference/admin/unified-model-mappings) |
| `truto.environmentUnifiedModelResourceMethods()` | `/environment-unified-model-resource-method` | [Environment unified model mappings](/docs/api-reference/admin/installed-unified-models-environment-unified-model-mappings) |

```java
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](/docs/api-reference/admin/sync-jobs) |
| `truto.syncJobRuns()` | `/sync-job-run` | | [Sync job runs](/docs/api-reference/admin/sync-job-runs) |
| `truto.syncJobCronTriggers()` | `/sync-job-cron-trigger` | `schedule(id)`: `POST /sync-job-cron-trigger/{id}/schedule` | [Sync job cron triggers](/docs/api-reference/admin/sync-job-cron-triggers) |
| `truto.syncJobTemplates()` | `/sync-job-template` | | [Sync job templates](/docs/api-reference/admin/sync-job-templates) |
| `truto.syncJobRunStates()` | `/sync-job-run-state` | | [Sync job run state](/docs/api-reference/admin/sync-job-run-state) |

```java
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](/docs/api-reference/admin/webhooks).

```java
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](/docs/api-reference/admin/workflows), [Workflow runs](/docs/api-reference/admin/workflowruns).

```java
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](/docs/api-reference/admin/datastores).

| 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](/docs/api-reference/admin/static-gates).

| Method | Endpoint |
|---|---|
| `list`, `get`, `create`, `update`, `delete` | `/static-gate`, `/static-gate/{id}` |

## Observability destinations

Accessible via `truto.observabilityDestinations()`. [API reference](/docs/api-reference/admin/observability-destinations).

```java
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](/docs/api-reference/admin/documentation).

| Method | Endpoint |
|---|---|
| `list`, `get`, `create`, `update`, `delete` | `/documentation`, `/documentation/{id}` |

## Batch requests

Accessible via `truto.batchRequests()`. [API reference](/docs/api-reference/admin/batch-requests).

| Method | Endpoint |
|---|---|
| `create(body)` | `POST /batch-request` |

## Analytics

Accessible via `truto.analytics()`. [API reference](/docs/api-reference/admin/analytics). Pass query parameters to the long form of each method.

```java
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](/docs/api-reference/admin/environments) |
| `truto.apiTokens()` | `list`, `get` | `/api-token` | [API tokens](/docs/api-reference/admin/api-tokens) |
| `truto.categories()` | `list` | `/category` | [Categories](/docs/api-reference/admin/categories) |
| `truto.logs()` | `list` | `/log` | [Logs](/docs/api-reference/admin/logs) |

`truto.logs().list(...)` needs a `log_type` query parameter: `unified_proxy_api`, `rapid_bridge`, `webhook`, `mcp`, or `sync_job_cron_trigger`.

```java
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](/docs/guides/daemon/overview).

| Accessor | Methods | Endpoint | Reference |
|---|---|---|---|
| `truto.daemons()` | `list`, `get`, `create`, `update`, `delete` | `/daemon`, `/daemon/{id}` | [Daemons](/docs/api-reference/admin/daemons) |
| `truto.daemonJobs()` | `list`, `get`, `create`, `update`, `delete` | `/daemon-job`, `/daemon-job/{id}` | [Daemon jobs](/docs/api-reference/admin/daemon-jobs) |
| `truto.daemonJobRuns()` | `list`, `get`, `create`, `update`, `delete` | `/daemon-job-run`, `/daemon-job-run/{id}` | [Daemon job runs](/docs/api-reference/admin/daemon-job-runs) |
| `truto.daemonJobCronTriggers()` | `list`, `get`, `create`, `update`, `delete`, `schedule` | `/daemon-job-cron-trigger`, `/daemon-job-cron-trigger/{id}` | [Daemon job cron triggers](/docs/api-reference/admin/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:

```java
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()`.
