Six Environment operations and five File HTTP operations are implemented in synchronous, asynchronous and withResponse() forms.
getDownloadUrl and download use the same QCA GET /files/{id}/content endpoint, so they count as one HTTP operation. The latter also retrieves the content from the returned storage URL.
import com.qoder.cloudagents.core.RequestOptions;
import com.qoder.cloudagents.forward.*;
import java.util.Collections;
Environment environment = client.environments().create(
EnvironmentCreateParams.builder()
.name("Java development")
.config(EnvironmentConfig.builder()
.type("cloud")
.packages(EnvironmentPackages.builder()
.npm(Collections.singletonList("pnpm@9"))
.pip(Collections.singletonList("PyYAML==6.0.1"))
.build())
.setupScript("echo environment-ready")
.build())
.build(),
RequestOptions.builder()
.idempotencyKey(java.util.UUID.randomUUID().toString())
.build());Omitting config lets the service default to cloud. Explicit config must have a type; the service rejects null, empty config and unsupported combinations. Self-hosted config permits type("self_hosted") and an optional setup script, not packages. Scripts are sent as text; creating a configuration does not execute them locally.
EnvironmentPackages has request setters for apt/npm/pip and response getters for all package managers, including reserved cargo/gem/go fields. It does not invent package defaults or send the response-only type field. Unknown fields remain accessible through field(...) / additionalField(...).
Environment update metadata is a server-side merge; this differs from Memory metadata replacement. The SDK sends only supplied keys, without fetching or merging state locally. Omitted fields, explicit null and empty maps stay distinct; the server rejects null metadata/config and the reserved metadata key created_by.
Environment responses also expose archived time, Identity ownership, icon URL and typed Template binding counts. Archive is separate from deletion. Delete does not silently archive or unbind a referenced Environment. Both Environment and File delete methods return typed JSON deletion results; a legacy HTTP 204/205 returns null data and retains status/headers in withResponse().
For PAT access to an Identity-owned resource, use:
RequestOptions identityScope = RequestOptions.builder()
.query("identity_id", "idn_example")
.build();
Environment scoped = client.environments().retrieve("env_example", identityScope);This query option applies to all Environment and File methods, including upload and the QCA download grant. The list builders also provide identityId(...). The parameter stays in the query; it is never added to JSON or multipart data. For SAT access the credential determines ownership; do not explicitly supply identity_id. The SDK does not change authentication or hide service errors based on this field.
Environment/File lists use page / next_page by default. afterId and beforeId are also available; all three selectors are mutually exclusive. Opaque page tokens, filters, Identity scope and request options carry into subsequent requests. Ordering is unchanged, repeated cursors fail, and exhausted pages require checking hasNextPage().
File name(...) is the existing list filter, not the deferred beta Search API. File scopeId(...) filtering has no usable cursor continuation in the current contract. Use data() for that response. Combining scope with cursors is rejected; attempting continuation of a scope-filtered response raises PaginationException rather than silently requesting a duplicate page.
import com.qoder.cloudagents.core.Upload;
import java.nio.charset.StandardCharsets;
FileMetadata file = client.files().upload(
FileUploadParams.builder()
.file(Upload.bytes("Hello QCA\n".getBytes(StandardCharsets.UTF_8),
"example.txt", "text/plain"))
.name("Example")
.purpose("session_resource")
.metadata(Collections.singletonMap("source", "java-example"))
.build(),
RequestOptions.builder()
.idempotencyKey(java.util.UUID.randomUUID().toString())
.build());The multipart file part carries raw bytes and a filename/MIME type. Metadata is a JSON string form field. Name and purpose are form fields; idempotency is a header. Batch input can use the existing purpose("session_resource") field. The multipart boundary is generated by the SDK and takes precedence over a manually supplied Content-Type.
Do not reuse an arbitrary InputStream source after invoking upload. Keep a Path stable until the request finishes. Invalid construction arguments fail before ownership transfers. Slow custom InputStreams must implement close so that it unblocks reads; the SDK cannot forcibly stop arbitrary user code.
The service currently documents text-like uploads and a 500 MiB limit; it validates supported formats, sizes, metadata and reserved keys. The SDK does not reinterpret PDF examples as proof that arbitrary binary types are accepted.
import com.qoder.cloudagents.core.FileDownload;
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
Path destination = Paths.get("downloaded-example.txt"); // chosen by your application
try (FileDownload download = client.files().download(file.id());
InputStream body = download.body()) {
java.nio.file.Files.copy(body, destination);
}The SDK first requests the download grant with QCA credentials, then makes a separate HTTP(S) storage request with no QCA Authorization, default/custom API headers, Identity query or idempotency key. The signed URL's own query is preserved. Up to five HTTP redirects are allowed on the storage hop, with no credentials added and no HTTPS downgrade. URLs with embedded credentials, non-HTTP(S) schemes or fragments are rejected.
download resolves when storage headers arrive, not after loading the entire file into memory. Close FileDownload (or its body) after use; EOF also releases the connection. Use requestId() for the QCA grant ID and storageRequestId() / headers() for the storage response. withResponse().download(...) exposes storage status and headers; its data must still be closed.
The regular timeout controls storage header wait and read idle time. An optional totalTimeout starts before the grant and remains active through storage retries and body consumption. Cancelling the pending async future cancels its active hop; after delivery, close the download. Closing the owning client aborts open downloads.
Storage header/status failures can retry under the shared GET policy. Once a stream is handed to the caller, a read error is surfaced as an SDK request exception and does not restart the file, avoiding duplicated output. The SDK does not overwrite local files or choose a path from a remote filename.
If you only need the grant:
FileDownloadUrl grant = client.files().getDownloadUrl(file.id());
// grant.url(), grant.expiresAt(), grant.filename()Treat signed URLs as credentials; do not log them. This helper does not fetch content.
EnvironmentFileExample creates an isolated Environment and text File, compares downloaded bytes in bounded chunks, then deletes only its own resources. It retains cleanup errors without hiding the original failure. It does not start Sessions or execute a setup script.
See example commands and opt-in live testing. Default tests use mock services and the installed SDK JAR.
