Java 8 compatible client for the Forward API. The repository follows the resource and authentication structure of the QCA Go, Python and TypeScript SDKs, using Maven and Java builders.
Status: source available; not yet published to Maven Central. The current development version is 0.1.0-SNAPSHOT. Build from source to use the SDK until a public Maven release is available.
The SDK provides synchronous and asynchronous clients for Models, Templates, Identities, Sessions and Threads, Memory Stores, Environments, Files, Skills, Vaults and Credentials, Schedules, Batches, Channels and Pairing, Usage, Service Account Tokens and Drive. Shared support includes PAT/SA authentication, timeouts, retries, pagination, typed errors and request IDs. Session/Thread SSE supports explicit resumption; Realtime supports text, PCM audio and History. The API coverage records the 123-operation baseline and its exclusions. Managed APIs are outside this project's scope.
See the examples for authentication and resource workflows, and the maintenance guide for contributor checks and version updates.
./mvnw clean install
./mvnw -f examples/pom.xml verify
python3 .github/scripts/check_package.pyRequires JDK 8 or later. JDK 17 is recommended for maintenance builds; compilation targets Java 8. The independent examples project consumes the installed JAR. See CONTRIBUTING.md for checks and repository organization; CI checks Java 8, 11, 17, 21 and 25.
import com.qoder.cloudagents.ForwardClient;
import com.qoder.cloudagents.forward.Template;
try (ForwardClient client = ForwardClient.builder()
.baseUrl("https://api.qoder.com/api/v1/forward")
.pat(System.getenv("QODER_PAT"))
.build()) {
Template template = client.templates().retrieve("tmpl_example");
System.out.println(template.id());
System.out.println(template.requestId());
}The default BaseURL is Global. For CN, use https://api.qoder.com.cn/api/v1/forward. QODER_FORWARD_BASE_URL provides the default when .baseUrl(...) is omitted.
With no explicit credentials, PAT mode reads QODER_ACCESS_TOKEN, then QODER_PAT. Explicit .pat(...), .accessToken(...) or .credential(...) takes precedence over environment tokens; configure only one explicit source.
import com.qoder.cloudagents.ForwardClient;
import com.qoder.cloudagents.auth.Region;
import com.qoder.cloudagents.auth.ServiceAccountCredential;
try (ServiceAccountCredential credential = ServiceAccountCredential.builder()
.saKey(System.getenv("QODER_SA_KEY"))
.region(Region.GLOBAL)
.scopes("qca.access", "forward.access")
.build();
ForwardClient client = ForwardClient.builder().credential(credential).build()) {
System.out.println(client.models().list().data().size());
}The credential lazily exchanges the SA Key for a platform SAT, caches and refreshes the SAT before expiry, and shares concurrent refreshes. Business requests use the SAT; the Key only goes to the fixed regional exchange endpoint. Set both Region.CN and the CN BaseURL for CN requests. No SA credential is inferred merely because QODER_SA_KEY exists.
The caller owns the credential. Keep it open until all clients using it are closed. Closing a client does not close its credential.
For a SAT managed by your application:
ForwardClient client = ForwardClient.builder()
.authMode(com.qoder.cloudagents.auth.AuthMode.SA)
.accessToken(System.getenv("QODER_ACCESS_TOKEN"))
.build();Forward SAT management uses explicit per-call SA Keys through serviceAccountTokens() and identities().serviceAccountTokens(); see the usage guide. A client without a business credential can call those methods; ordinary requests fail locally before network access until a business credential is configured.
Close the client after use. A static SAT is not automatically renewed. SA mode does not fall back to QODER_PAT. For application-managed rotation, implement Credential.getToken() returning a CompletableFuture<String>; it is called before each attempt and must be thread-safe and return promptly. Cancelling a request does not cancel a shared credential future.
java.util.concurrent.CompletableFuture<com.qoder.cloudagents.forward.Template> pending =
client.async().templates().retrieve("tmpl_example");
pending.cancel(true); // when the caller no longer needs the resultCancellation propagates to the HTTP call and retry timer. The returned future follows Java's CancellationException contract. Interrupting a synchronous call cancels its request, preserves the interrupt flag and raises ApiUserAbortException.
client.async() shares its parent's resources; closing the view is a no-op. Closing the parent cancels active requests. ForwardClient.builder().buildAsync() creates an independently owned, closeable async client. Keep the owning client open until futures finish. Additional futures created with application-side thenApply(...) follow normal Java cancellation behavior; cancel the SDK-returned future to abort its request.
com.qoder.cloudagents.core.RequestOptions options =
com.qoder.cloudagents.core.RequestOptions.builder()
.timeout(java.time.Duration.ofSeconds(30))
.totalTimeout(java.time.Duration.ofSeconds(60))
.maxRetries(1)
.build();
com.qoder.cloudagents.core.ApiResponse<com.qoder.cloudagents.forward.Template> response =
client.templates().withResponse().retrieve("tmpl_example", options);- Default connection timeout: 5 seconds. Default response-header wait and read/write idle timeouts: 10 minutes.
.timeout(...)overrides the latter limits for one attempt. Optional.totalTimeout(...)covers credentials, all attempts, backoff and body reads. Timeouts must be positive. SSE disables read-idle timing after accepted headers; an optional subscription-wide total deadline remains active, including reconnects. See SSE lifecycle. - Default maximum retries: 2, giving at most 3 attempts. The shared engine follows QCA safety rules: 409 is never retried; non-idempotent writes only qualify for 429 retries; non-repeatable bodies are not retried. Server retry hints remain subject to these checks.
ApiExceptioncovers request failures.ApiStatusExceptionexposes HTTP status, request ID, headers and parsed error body. Connection, timeout, cancellation and response parsing failures have corresponding subclasses. SA issuer failures useServiceAccountExchangeException, exposing status/request ID without retaining the issuer body. Failed SA exchange does not switch to PAT.- Models provide
requestId(),field(name)for unknown fields andrawJson(). Unknown fields and numeric precision are preserved; invalid typed field access raisesResponseValidationException. Request metadata is separate from the JSON body. - Template list parameters support
limit,status,afterIdorbeforeId. Usepage.data()for one page,getNextPage()/getNextPageAsync()for manual pagination, orfor (Template template : page)for lazy synchronous iteration. Server order is retained; filters and request options follow subsequent pages. CheckhasNextPage()before fetching; an exhausted page or repeated cursor raisesPaginationException.
import com.qoder.cloudagents.forward.TemplateCreateParams;
import com.qoder.cloudagents.forward.TemplateUpdateParams;
import com.qoder.cloudagents.core.RequestOptions;
Template template = client.templates().create(
TemplateCreateParams.builder()
.name("Support assistant")
.modelId("auto")
.environmentId("env_example")
.build(),
RequestOptions.builder().idempotencyKey(java.util.UUID.randomUUID().toString()).build());
client.templates().update(template.id(), TemplateUpdateParams.builder()
.description("Updated description")
.multiagent(null) // explicitly clear the multi-agent configuration
.build());An unset builder field is omitted. A setter with null writes JSON null; empty collections write [] or {}. The server decides which fields accept null. omit("wire_name") removes a previously set field. Requests and responses preserve unknown JSON fields; raw JSON can contain secrets and should not be logged. See Template operations and configuration and the lifecycle example.
- Runnable examples and shared PAT/SA setup
- Template operations, configuration and pagination
- Identity operations and configuration overrides
- Session HTTP operations, events, threads and Memory Store mounts
- Session/Thread SSE, cancellation and resumable streaming
- Memory Stores, content updates, versions and lifecycle rules
- Environments, multipart upload and streaming file download
- Skills, versions, Vaults and Credentials
- Schedules, Runs and Batches
- Channels, Pairing and QR authorization
- Usage, Forward SAT management and Drive
- Forward API coverage
- Realtime text, PCM audio and History
- Opt-in live testing
- Maintenance and release setup
- Changelog
./mvnw verify also builds source and Javadoc JARs. API documentation is generated under target/reports/apidocs/; packages under core.internal are implementation details.