# Installation

Install a first-party Lockwell SDK and point it at a running daemon. Go, Node, and Java ship S3, HTTP-native JSON, and
Admin clients. The opt-in Native Wire packages add a binary LNW/1 transport for qualified server runtimes; they never
silently switch protocols. Start with the [surface guide](/guide/the-three-surfaces) and [Native Wire setup](/guide/native-wire).

::: info Early access
Lockwell is closed source during early access. The package versions on this page are historical builds; early-access
customers receive current packages, the deployment repository and a licence with their access. Request access at
[hello@rusticstack.com](mailto:hello@rusticstack.com). An MIT-licensed source release is planned.
:::

## Prerequisites

You need a running `lockwelld` with two listeners reachable from your app:

| Listener   | Carries                                         | Example                 |
| ---------- | ----------------------------------------------- | ----------------------- |
| **public** | the S3 API, native JSON API (`/api/v1/`), and optional LNW listener | `http://localhost:9000` / `127.0.0.1:9444` |
| **admin**  | the JSON Admin API (`/admin/api/v1/`)           | `http://localhost:9001` |

Endpoints come from your deployment. See [Deployment](/guide/deployment) to stand one up. Credentials come from Lockwell
itself:

- An **admin API token** (`lwadm_...`) is minted offline on the server host with
  `lockwell admin-token create --role owner`. Use it for the Admin API and the app kit.
- **Access keys** (an `accessKeyId` + `secretKey`) are minted **through** the Admin API or the app kit's
  `provisionTenant`, per tenant. Use them for the S3 and native data planes. See
  [Tenancy and auth](/guide/tenancy-and-auth).

## Node

```sh
npm i @kelphect/sdk
```

Requires **Node.js 22 or newer** (global `fetch`, `crypto`, `ReadableStream`; the package-root LNW/1 client requires
the `@kelphect/sdk-native` peer, which is Node 22+). The package ships both ESM and CommonJS,
so both styles work:

```js
import { Client, NativeClient, AdminClient, LockwellKit } from "@kelphect/sdk"; // ESM
const { Client } = require("@kelphect/sdk"); // CJS
```

::: tip GitHub Packages The package is published privately as `@kelphect/sdk` on the GitHub Packages registry. A
one-time auth setup is required. Put a classic GitHub PAT with `read:packages` in `NODE_AUTH_TOKEN` and point the
`@kelphect` scope at GitHub Packages in your `.npmrc`. :::

### Edge runtimes

On Cloudflare Workers, Vercel Edge, Bun, or Deno, import from the dedicated edge entry instead of the default barrel:

```js
import { NativeClient, AdminClient, LockwellKit, verifyWebhook } from "@kelphect/sdk/edge";
```

`@kelphect/sdk/edge` re-exports only the `node:*`-free surface (the native client, admin client, app kit,
`verifyWebhook`, `RetryPolicy`, `sha256ChecksumBase64`), so a bundler produces a bundle with zero `node:crypto` and no
`nodejs_compat` flag. The S3 `Client` is deliberately not exported there. Its SigV4 signer is Node-only. See
[Edge runtimes](/guide/edge-runtimes).

### Native Wire (Node/Bun servers)

For raw LNW/1 binary transport, install the separate server-only package. It is not the same as the historical
`@kelphect/sdk/edge` HTTP-native entry:

```sh
npm i @kelphect/sdk-native@0.1.0
# or: bun add @kelphect/sdk-native@0.1.0
```

`@kelphect/sdk-native` requires Node 22+ or Bun 1.4+. Import `@kelphect/sdk-native/node` or `/bun` explicitly when
desired; the root condition selects the runtime. Browser/default conditions throw, and there is no JSON/S3 fallback.
See the [Native TypeScript guide](/sdks/bun-native) for TLS CA/mTLS, pooling, streaming, and retry configuration.

## Go

All first-party Go SDK packages live in the private
[`RusticStack/lockwell-sdk-go`](https://github.com/RusticStack/lockwell-sdk-go) module. Configure GitHub auth for
private modules, then install the module and import only the packages you use:

```sh
go env -w GOPRIVATE=github.com/RusticStack/*
```

```sh
# All first-party Go SDK packages are in this one module.
go get github.com/RusticStack/lockwell-sdk-go/lockwellsdk@latest
```

Each package's non-test source imports only the standard library (plus a small errors helper), so you pull a lightweight
dependency tree.

```go
import (
 "github.com/RusticStack/lockwell-sdk-go/lockwellkit"
 "github.com/RusticStack/lockwell-sdk-go/lockwelladmin"
 "github.com/RusticStack/lockwell-sdk-go/lockwellnative"
)
```

## Java

The coordinate is `com.lockwell:lockwell-sdk`, published to **GitHub Packages** (configure the GitHub Packages
repository and a token with `read:packages` in your build, the same as any GitHub-Packages dependency). It requires
**JDK 25**. The main artifact owns the binary LNW/1 client; S3 and HTTP/JSON clients remain explicit compatibility
surfaces and the Admin client remains a separate JSON control plane. The optional Spring starter supplies only Boot
4.1.1 autoconfiguration for that same wire core.

::: code-group

```xml [Maven]
<dependency>
  <groupId>com.lockwell</groupId>
  <artifactId>lockwell-sdk</artifactId>
  <version>0.2.2</version>
</dependency>
```

```groovy [Gradle]
dependencies {
    implementation 'com.lockwell:lockwell-sdk:0.2.2'
}
```

:::

The three surfaces live in distinct packages:

```java
import com.lockwell.sdk.*;            // S3 data plane: LockwellClient
import com.lockwell.sdk.nativeapi.*;  // native data plane: LockwellNativeClient
import com.lockwell.sdk.springwire.*; // binary LNW/1 data plane
import com.lockwell.sdk.admin.*;      // Admin API: LockwellAdminClient
import com.lockwell.sdk.kit.*;        // app kit: LockwellKit
```

For Spring Boot 4.1, add `com.lockwell:lockwell-spring-boot-starter` to enable lifecycle-managed LNW/1 sync and async
beans. For other Spring or plain JDK applications, construct `LockwellNativeWireClient` directly. All clients are
thread-safe.

### Java private Maven setup

`com.lockwell:lockwell-sdk` is published privately to GitHub Packages for the `RusticStack/lockwell-sdk-java`
repository. Configure the repository in the application build and put credentials in Maven/Gradle settings, not in
source.

```xml [Maven repository]
<repositories>
  <repository>
    <id>github</id>
      <url>https://maven.pkg.github.com/RusticStack/lockwell-sdk-java</url>
  </repository>
</repositories>
```

```xml [~/.m2/settings.xml]
<settings>
  <servers>
    <server>
      <id>github</id>
      <username>x-access-token</username>
      <password>${env.GITHUB_PACKAGES_TOKEN}</password>
    </server>
  </servers>
</settings>
```

Use a GitHub token with `read:packages` for developer machines, CI, and on-prem build runners. If the consuming
repository is private and separate from `RusticStack/lockwell-sdk-java`, the token also needs repository access that
can read the private package. In GitHub Actions, write `settings.xml` from secrets and pass the token through the
environment; never commit tokens or generated Maven settings.

Pin immutable versions (`0.2.2`, not a floating range). Maven/GitHub Packages publish the binary jar, POM, sources jar,
and repository-generated checksums for each version. Support bundles and offline install media should mirror those exact
artifacts, their checksums, the release tag, and the Lockwell supply-chain evidence (`make supply-chain`: module
verification, vulnerability report, and SBOM). The Java SDK itself has no third-party runtime dependency tree for the
core, native, admin, or kit clients.

Maven Central is deferred for this adoption gate. Until a future release explicitly adds Maven Central publishing,
customer-facing and offline TangibleShift builds should use an internal/customer artifact mirror seeded from the vetted
GitHub Packages artifact, not a developer laptop cache.

### Spring Boot Native Wire starter

Spring services that need LNW/1 install the separate JDK 25-first starter:

```xml
<dependency>
  <groupId>com.lockwell</groupId>
  <artifactId>lockwell-spring-boot-starter</artifactId>
  <version>0.2.2</version>
</dependency>
```

Enable `lockwell.native-wire.enabled=true`, configure TLS CA/mTLS properties, and keep access-key material in an
environment-backed secret store. The starter is compiled with `--release 25`; its health, metrics, async, Object Lock,
metadata, and streaming contract is documented in [Spring Boot Native Wire](/sdks/java-spring-wire). It is source/test
qualified in this repository, not an assertion that your package mirror has published it.

### Java 25 and API compatibility

The SDK is compiled with `--release 25` and supports JDK 25. LNW/1 is a direct binary transport; it is not HTTP/JSON or
S3 compatibility wrapping. The starter is compiled against Spring Boot 4.1.1, with a Spring Boot 4.0 external-consumer
lane. Java 21 bytecode/runtime compatibility is not claimed.

Pin the SDK and Lockwell server/image to the same release line unless a release note says otherwise. The Java SDK wraps
the versioned JSON Admin API at `/admin/api/v1` and native API at `/api/v1`; long-lived on-prem installs can compare the
server's published OpenAPI documents (`/admin/api/v1/openapi.json`, `/api/v1/openapi.json`) with the SDK version they
ship. A mismatch should fail the installer or support preflight before tenant provisioning starts.

## Client options

Every client takes the same core inputs (an endpoint plus credentials) and a few optional knobs. The optional ones share
names across surfaces.

| Option                  | What it sets                                                          | Default                         |
| ----------------------- | --------------------------------------------------------------------- | ------------------------------- |
| `httpClient` / `fetch`  | The transport. Inject your own to control timeouts, pooling, and TLS. | A per-client default            |
| `userAgent`             | The `User-Agent` header sent on every request.                        | `lockwell-<lang>-<surface>/0.1` |
| `retry` / retry policy  | Automatic retry of safe requests on transient failures.               | See [Retry setup](#retry-setup) |
| `requestTimeout` (Java) | Per-request timeout on the JDK `HttpClient`.                          | None                            |

Construct an S3 client with a custom transport:

::: code-group

```ts [Node]
import { Client } from "@kelphect/sdk";

const s3 = new Client({
  endpoint: "http://localhost:9000",
  accessKeyId: process.env.LOCKWELL_ACCESS_KEY_ID,
  secretKey: process.env.LOCKWELL_SECRET_KEY,
  userAgent: "acme-billing/1.4",
});
```

```go [Go]
import (
 "net/http"
 "time"

 "github.com/RusticStack/lockwell-sdk-go/lockwellsdk"
)

hc := &http.Client{Timeout: 30 * time.Second}
s3, err := lockwellsdk.New("http://localhost:9000",
 lockwellsdk.Credentials{
  AccessKeyID: os.Getenv("LOCKWELL_ACCESS_KEY_ID"),
  SecretKey:   os.Getenv("LOCKWELL_SECRET_KEY"),
 },
 lockwellsdk.WithHTTPClient(hc),
 lockwellsdk.WithUserAgent("acme-billing/1.4"),
)
```

```java [Java]
import com.lockwell.sdk.*;
import java.net.http.HttpClient;
import java.time.Duration;

LockwellClient s3 = LockwellClient.builder()
    .endpoint("http://localhost:9000")
    .credentials(new Credentials(
        System.getenv("LOCKWELL_ACCESS_KEY_ID"),
        System.getenv("LOCKWELL_SECRET_KEY")))
    .httpClient(HttpClient.newHttpClient())
    .userAgent("acme-billing/1.4")
    .requestTimeout(Duration.ofSeconds(30))
    .build();
```

:::

The native and admin clients accept the same `httpClient`/`userAgent` options. The native client also takes
`refreshSkewMs` (Node) to control how far ahead of expiry it refreshes the bearer token.

::: warning Secrets never render Clients redact the secret (and, for the native client, the live bearer token) from
`toString` / `inspect` / JSON. Logging a client object never leaks credentials. :::

## Retry setup

The S3 clients retry safe requests on transient failures: a transport error, a `5xx`, or a `429`. A request is only ever
replayed when replay is safe.

- `GET`, `HEAD`, and `DELETE` are idempotent, so they retry freely.
- A buffered-body `PUT`/`POST` retries only when it carries an idempotency key, so the server collapses duplicate
  effects.
- Streaming uploads never retry, because the source is already consumed.

Backoff is exponential (100ms base, doubling, 2s cap) with full jitter.

The defaults differ by language, so set the policy explicitly when it matters:

| SDK  | S3 client default                     | Native client                                              |
| ---- | ------------------------------------- | ---------------------------------------------------------- |
| Go   | on (`DefaultRetryPolicy`, 3 attempts) | mints a token and retries once on `401`                    |
| Node | off (1 attempt)                       | mints a token and retries once on `401`                    |
| Java | off (1 attempt)                       | on (`RetryPolicy.defaults()`) plus one `401` token re-mint |

::: code-group

```ts [Node]
import { Client, RetryPolicy } from "@kelphect/sdk";

const s3 = new Client({
  endpoint: "http://localhost:9000",
  accessKeyId: process.env.LOCKWELL_ACCESS_KEY_ID,
  secretKey: process.env.LOCKWELL_SECRET_KEY,
  retry: RetryPolicy.default(), // opt in (3 attempts)
  // retry: new RetryPolicy({ maxAttempts: 5, baseBackoffMs: 200 }), // tune it
  // retry: RetryPolicy.disabled(),             // the default
});
```

```go [Go]
import "github.com/RusticStack/lockwell-sdk-go/lockwellsdk"

s3, err := lockwellsdk.New(endpoint, creds,
 lockwellsdk.WithRetryPolicy(lockwellsdk.DefaultRetryPolicy()),  // the default
 // lockwellsdk.WithRetryPolicy(lockwellsdk.RetryPolicy{MaxAttempts: 5, BaseBackoff: 200 * time.Millisecond, MaxBackoff: 2 * time.Second, Jitter: 1.0}),
 // lockwellsdk.WithRetryPolicy(lockwellsdk.DisabledRetryPolicy()),
)
```

```java [Java]
import com.lockwell.sdk.*;
import java.time.Duration;

LockwellClient s3 = LockwellClient.builder()
    .endpoint(endpoint)
    .credentials(creds)
    .retryPolicy(RetryPolicy.defaults())                                   // opt in (3 attempts)
    // .retryPolicy(RetryPolicy.of(5, Duration.ofMillis(200), Duration.ofSeconds(2), 1.0)) // tune it
    // .retryPolicy(RetryPolicy.disabled())                                // the default
    .build();
```

:::

For an idempotency-keyed write to be retried, set the idempotency key on the put. See
[Conditional writes and idempotency](/guide/conditional-writes) and [Errors and retries](/guide/errors-and-retries).

The Java native client uses the same `RetryPolicy` type and enables `RetryPolicy.defaults()` by default. It retries
`GET`, `HEAD`, and `DELETE` automatically, and retries `PUT` or `POST` only when the request carries `Idempotency-Key`.
See [Java native client](/sdks/java-native).

## Verify the install

A minimal native-client round-trip confirms the endpoint and credentials are wired correctly:

::: code-group

```ts [Node]
import { NativeClient } from "@kelphect/sdk";

const native = new NativeClient({
  endpoint: "http://localhost:9000",
  accessKeyId: process.env.LOCKWELL_ACCESS_KEY_ID,
  secretKey: process.env.LOCKWELL_SECRET_KEY,
});
console.log(await native.listBuckets());
```

```go [Go]
nc, err := lockwellnative.New("http://localhost:9000",
 os.Getenv("LOCKWELL_ACCESS_KEY_ID"), os.Getenv("LOCKWELL_SECRET_KEY"))
if err != nil {
 log.Fatal(err)
}
buckets, err := nc.ListBuckets(context.Background())
if err != nil {
 log.Fatal(err)
}
log.Println(buckets)
```

```java [Java]
LockwellNativeClient nc = LockwellNativeClient.builder()
    .endpoint("http://localhost:9000")
    .accessKeyId(System.getenv("LOCKWELL_ACCESS_KEY_ID"))
    .secretKey(System.getenv("LOCKWELL_SECRET_KEY"))
    .build();
System.out.println(nc.listBuckets());
```

:::

## Next steps

- [Getting started](/guide/getting-started). The full five-minute walkthrough.
- [The three surfaces](/guide/the-three-surfaces). Pick the right client.
- [Go SDK](/sdks/go), [Node SDK](/sdks/node), [Java SDK](/sdks/java). Per-language reference.
