# Client configuration

All three SDKs expose S3, native JSON, and admin clients, but the listeners and credentials are deliberately separate.
Use the public object endpoint for S3 and native clients; use the private admin endpoint only for admin clients.

## Endpoint and path prefixes

An endpoint may include a clean reverse-proxy prefix, such as `https://objects.example.com/lockwell`. The SDK preserves
that prefix in normal requests, SigV4 canonical paths, and presigned URLs. Do not append `/api/v1` or `/admin/api/v1`
yourself unless the proxy exposes that exact mounted URL; duplicate suffixes are normalized once. User information,
query strings, fragments, traversal segments, encoded separators, and malformed escapes are rejected.

Path-style S3 addressing is the default. Enable virtual-hosted style only when wildcard bucket DNS and certificates are
configured. Dotted bucket names remain part of the signed host.

The S3 signing region defaults to `us-east-1`; it must match the server-configured region. Native and admin bearer-token
requests do not use a signing region.

## TLS and a private CA

Production endpoints should use TLS. A private CA belongs in the injected transport, not in an SDK-wide “skip verify”
flag. Never disable certificate verification.

::: code-group

```go [Go]
roots, err := x509.SystemCertPool()
if err != nil { log.Fatal(err) }
pem, err := os.ReadFile("/run/secrets/lockwell-ca.pem")
if err != nil || !roots.AppendCertsFromPEM(pem) { log.Fatal("invalid Lockwell CA") }

httpClient := &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{
    MinVersion: tls.VersionTLS12,
    RootCAs: roots,
}}}
s3, err := lockwellsdk.New(endpoint, creds,
    lockwellsdk.WithHTTPClient(httpClient),
    lockwellsdk.WithRequestTimeout(30*time.Second))
```

```ts [Node]
import https from "node:https";
import { readFileSync } from "node:fs";
import { Client, createNodeFetch } from "@kelphect/sdk";

const httpsAgent = new https.Agent({
  keepAlive: true,
  ca: readFileSync("/run/secrets/lockwell-ca.pem"),
});
const transport = createNodeFetch({ httpsAgent, maxSockets: 64 });
const s3 = new Client({ endpoint, accessKeyId, secretKey, fetch: transport, timeoutMs: 30_000 });
// Call transport.close() during process shutdown. Caller-supplied agents remain caller-owned.
```

```java [Java]
KeyStore trust = KeyStore.getInstance(KeyStore.getDefaultType());
try (InputStream in = Files.newInputStream(Path.of("/run/secrets/lockwell-truststore.p12"))) {
    trust.load(in, System.getenv("LOCKWELL_TRUSTSTORE_PASSWORD").toCharArray());
}
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trust);
SSLContext ssl = SSLContext.getInstance("TLS");
ssl.init(null, tmf.getTrustManagers(), null);

HttpClient http = HttpClient.newBuilder().sslContext(ssl).build();
LockwellClient s3 = LockwellClient.builder()
    .endpoint(endpoint).credentials(credentials).httpClient(http)
    .requestTimeout(Duration.ofSeconds(30)).build();
```

:::

The JVM-wide alternative is `-Djavax.net.ssl.trustStore=/path/lockwell-truststore.p12` plus
`-Djavax.net.ssl.trustStorePassword=...`; prefer an injected `HttpClient` when one process talks to services with
different trust roots.

## Timeouts, retries, and cancellation

Treat the per-attempt timeout and the caller deadline as different budgets. A retryable operation can consume several
attempt timeouts plus backoff; a caller deadline or cancellation stops the whole operation.

| Language | Attempt timeout                     | Whole-operation cancellation                                                      | Retry default                                                               |
| -------- | ----------------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| Go       | `WithRequestTimeout`                | `context.Context` deadline/cancel                                                 | S3/native/admin use their documented policy; disable explicitly when needed |
| Node     | `timeoutMs` (bounded to 30 seconds) | caller-owned `AbortSignal`                                                        | S3 one attempt; native/admin three attempts                                 |
| Java     | `.requestTimeout(Duration)`         | synchronous interruption/HTTP state; `CompletableFuture.cancel` for async wrapper | S3 opt-in; native/admin default retries                                     |

Retries are limited to transient transport failures, 429, and bounded 5xx responses when the request is replay-safe.
Buffered writes need an idempotency key; one-shot streams and ambiguous admin mutations are not replayed.
Authentication, authorization, checksum, retention, legal-hold, quota, and ordinary 4xx failures are not automatic retry
candidates.

## Progress and streaming recovery

Progress callbacks run synchronously or are awaited, so a slow callback applies backpressure. Callback failure cancels
the request. Go reports cumulative bytes and a known/unknown total. Node and Java also report direction, chunk bytes,
and multipart part number where applicable. None of the SDKs claims transparent stream resume: recover with explicit
range reads or multipart list/abort APIs and an application-owned checkpoint.

## Response metadata and audit correlation

Configure `WithResponseMetadata`, `onResponseMetadata`, or `responseMetadataListener` to observe successful `requestId`,
optional Amazon request id, and optional `traceparent`. Errors carry their request id on the typed error. Store these
identifiers beside an application job id; do not put secrets, bearer tokens, signed URL query strings, or object bytes
in logs.

## Concurrency and ownership

Clients are safe for concurrent use. Go clients may be shared between goroutines; Node clients share token minting and
pooled transports; Java clients are thread-safe. Individual paginator/iterator instances and streaming bodies are
operation-owned and should not be advanced or consumed concurrently. Close download bodies and any transport/executor
that the application owns.

## Related

- [Errors and retries](/guide/errors-and-retries)
- [Operations and observability](/guide/operations-and-observability)
- [SDK capability index](/reference/sdk-capabilities)
