Native data-plane API
The native JSON data-plane API lives at /api/v1/ on the public listener: the same host:port as the S3 API, a different path prefix. No SigV4 signing, no XML. JSON in, JSON out.
It is a new transport and auth layer over the same domain pipeline the S3 handler uses (the same object coordinator, encryption, dedup, checksums, versioning, object-lock, scope enforcement, quota, and audit). A native write is encrypted-at-rest, deduped, quota-checked, and retention-gated exactly like an S3 write.
For most apps, reach this API through a first-party SDK rather than calling it directly: Go, Node, or Java. The SDKs auto-manage the bearer token for you. This page is the wire-level reference.
Authentication
The native API does not use SigV4 on data calls. A caller exchanges its existing S3 access key for a short-lived native bearer token.
Mint a token: POST /api/v1/auth/token
OAuth client-credentials style. Present your S3 access-key id plus secret either as HTTP Basic (Authorization: Basic base64(accessKeyId:secretKey)) or a JSON body {"accessKeyId": "...", "secretKey": "..."}. On success you get a short-lived bearer token:
lwtk_<base64url(payloadJSON)>.<base64url(HMAC-SHA256(payloadB64, signingKey))>- TTL is
security.native_api_token_ttl(default1h). Keep it short: a leaked token is replayable until it expires. - The token is stateless and signed (HMAC under a per-deployment key derived from the at-rest master key), so the hot path verifies it with no per-request DB lookup for the token itself.
- Revoking or expiring the underlying access key (or disabling the tenant) invalidates outstanding tokens promptly. The verify path re-checks revocation on every request, so a token can never outlive or out-scope the key it points at.
- This endpoint accepts the secret once, so it must run behind TLS. It is rate-limited per access-key id and audited (success and failure).
Send the token on every subsequent call as Authorization: Bearer <token>. The SDKs cache it until shortly before expiry, refresh transparently, and re-mint once on a 401.
The bearer token is replayable until it expires. Keep its TTL short and always mint it over TLS, since
auth/token accepts the secret in the clear. :::
Signed-URL auth (no bearer token)
GET|PUT /api/v1/signed/{bucket}/{key...}?token=… is authorized solely by the query token: an HMAC-signed, expiring URL token (lwurl_…) minted by POST /api/v1/sign-url, under a separate per-deployment key.
At access time the handler re-checks the underlying key's revocation, re-runs the per-operation scope and bucket-policy gates against the current scope, and enforces that the request method and bucket/key match the signed token. A tampered, expired, wrong-method, wrong-resource, revoked, or scope-exceeding URL is rejected (401/403). See signed URLs.
Routes
All routes except /healthz, /openapi.json, and /auth/token require a valid bearer token. The tenant is taken from the signed token, never the request path, so cross-tenant access is structurally impossible (another tenant's bucket returns 404, never a leak).
Buckets
| Method + path | Purpose |
|---|---|
GET /buckets | list the tenant's buckets |
POST /buckets | create a private bucket (versioning / object-lock options) |
GET /buckets/{bucket} | get a bucket |
DELETE /buckets/{bucket} | delete an empty bucket |
GET|PUT /buckets/{bucket}/versioning | read / set versioning state |
Objects
| Method + path | Purpose |
|---|---|
PUT /buckets/{bucket}/objects/{key...} | streaming upload |
GET /buckets/{bucket}/objects/{key...} | streaming download (Range supported) |
HEAD /buckets/{bucket}/objects/{key...} | metadata only |
DELETE /buckets/{bucket}/objects/{key...} | delete (delete marker in a versioned bucket) |
GET /buckets/{bucket}/objects | list (prefix / delimiter / maxKeys / continuationToken) |
POST /buckets/{bucket}/objects:batchDelete | batch delete, per-key results |
POST /buckets/{bucket}/object-copy/{key...} | same-tenant server-side copy |
The upload supports several controls:
- An
Idempotency-Keyheader, mapped onto the same idempotency store the S3X-Lockwell-Idempotency-Keypath uses. - Native conditional writes (
If-Match/If-None-Match). - Optional server-side checksum verification (
X-Lockwell-Checksum-<alg>for CRC32, CRC32C, CRC64NVME, SHA1, SHA256). A bad digest is rejected before any bytes are committed.
The copy source is the JSON body ({sourceBucket, sourceKey, sourceVersionId?, metadataDirective?, …, requireAbsent?, requireMatchEtag?}). Cross-tenant copy is impossible, since the source resolves under the token's tenant.
Versions, tags, and per-object WORM
| Method + path | Purpose |
|---|---|
GET /buckets/{bucket}/versions | list versions and delete markers (prefix / keyMarker / versionIdMarker) |
GET|PUT|DELETE /buckets/{bucket}/object-tags/{key...} | get / replace / clear the JSON tag set |
GET|PUT /buckets/{bucket}/object-retention/{key...} | per-object retention ({mode: GOVERNANCE|COMPLIANCE, retainUntil}) |
GET|PUT /buckets/{bucket}/object-legal-hold/{key...} | legal-hold status ({status: ON|OFF}) |
These four sub-resources use a distinct path prefix (not a suffix on /objects/{key...}) so the key wildcard preserves embedded slashes.
Retention can be extended but never shortened. There is no governance bypass on the native path, a deliberate
non-goal that stays closed. :::
Multipart
| Method + path | Purpose |
|---|---|
POST /buckets/{bucket}/multipart/{key...} | create an upload |
PUT /buckets/{bucket}/multipart/{uploadId}/parts/{partNumber}/{key...} | upload a part |
GET /buckets/{bucket}/multipart/{uploadId}/parts/{key...} | list parts |
POST /buckets/{bucket}/multipart/{uploadId}/complete/{key...} | complete |
DELETE /buckets/{bucket}/multipart/{uploadId}/{key...} | abort |
GET /buckets/{bucket}/multipart | list in-progress uploads |
Signed URLs
| Method + path | Purpose |
|---|---|
POST /sign-url (bearer) | mint a method-, resource-, and scope-bounded signed URL: {method, bucket, key, ttlSeconds?} |
GET|PUT /signed/{bucket}/{key...}?token=… (no bearer) | use a signed URL |
method is GET (download) or PUT (upload): the native API supports signed write URLs. The TTL is clamped to security.max_presign_ttl, and the URL can never exceed the minting key's live scope (a read-only key minting a PUT URL is 403).
Bucket event notifications
| Method + path | Purpose |
|---|---|
GET /buckets/{bucket}/notifications | get the configuration |
PUT /buckets/{bucket}/notifications | set it (an empty configs list clears it) |
DELETE /buckets/{bucket}/notifications | clear it |
Only the webhook target is supported. An sns/sqs/lambda target is 501. The per-config webhook signing secret is generated server-side (crypto/rand, sealed at rest) and is never returned on the wire; the response carries only hasSecret. See webhooks.
Native fields surfaced
Object reads surface Lockwell-native details S3 XML hides: native checksums (CRC32/CRC32C/CRC64NVME/SHA1/SHA256), encryption and compression status, storage class, version id, retention and legal-hold, and content length. They come back as JSON fields and X-Lockwell-* response headers.
Error shape (problem+json)
Failures return an application/problem+json body:
{
"code": "not_found",
"message": "bucket \"reports\" not found",
"status": 404,
"requestId": "req_01HXY…"
}code is a stable machine-readable string. requestId correlates the failure with its server-side audit row (also echoed in the X-Request-Id header). Status mapping:
| Status | Meaning |
|---|---|
401 | missing/invalid/expired bearer token, or a revoked access key |
403 | access-key scope or bucket-policy denial |
404 | bucket/key not found |
409 | bucket already exists |
412 | conditional-write or copy-source precondition not met |
501 | unsupported notification target (SNS/SQS/Lambda) |
507 | tenant storage quota exceeded |
Security parity
The native API is private by default and never served anonymously. Every control the S3 path enforces is enforced identically here, through the same domain services:
- Tenant isolation (from the token, never the path).
- Per-operation
read/write/delete/adminplus bucket/prefix scope enforcement. - Explicit-deny bucket policies.
- Encryption, dedup, quota, object-lock, retention, and legal-hold.
- Audit on every request, including denials.
It does not relax any S3 control or enable any public/anonymous access. See tenancy and auth.
OpenAPI
The full machine-readable contract is an OpenAPI 3 document, available two ways:
- Served live at
GET /api/v1/openapi.json(unauthenticated, since you need the contract before you hold a token). - Committed at
internal/nativeapi/openapi.json(the canonical source; the served document is the same bytes).
Each operation carries a unique operationId (putObject, listObjects, signURL, setBucketNotifications, …), so you can generate a client in any language with openapi-generator (make codegen produces TypeScript/Python/Go clients for both the native and admin specs). Prefer the first-party SDKs for Go, Node, and Java; codegen is the path for every other language.
Interactive reference
Every native operation below is generated from that OpenAPI document, so it always matches the shipped server. Expand an operation for its path and query parameters, request and response schemas, and copy-paste curl / fetch samples. The example host is a placeholder; swap in your own deployment.
First-class native JSON/HTTP data-plane API for Lockwell, mounted at /api/v1/ on the public (S3) listener. It is a new transport + auth over the same domain pipeline the S3 compatibility handler uses (object coordinator, object service, metastore, storage, policy, audit). Authentication is a short-lived native bearer token minted from an existing S3 access key (POST /auth/token); per-operation authorization reuses the same access-key scopes and bucket policies, so it never relaxes any S3 security control. The tenant is taken from the signed token, never the path.
Servers
Mint a native bearer token from an S3 access key
Mint a short-lived, scoped, signed native URL
Issues a time-limited HMAC-signed URL for an unauthenticated GET (download) or PUT (upload) of a single object. The URL is method-bound and resource-bound, TTL-clamped to security.max_presign_ttl, and can never exceed the minting key's scope (the live scope + bucket policy are re-checked here and again at access time). Body: {method, bucket, key, ttlSeconds?}.
Authorizations
Native data-plane bearer token (lwtk_...) minted at POST /auth/token. Stateless, HMAC-signed under a per-deployment key derived from the at-rest master key; short-lived (default 1h). Revoking the underlying access key invalidates outstanding tokens promptly.
Responses
Signed URL issued
Download via a signed URL (no bearer token)
Public object download authorized by the token query parameter (a method/resource-bound, short-lived, HMAC-signed URL token). Revocation and the per-operation scope/bucket-policy gates are re-checked at access time, so it grants nothing the minting key lacks.
Parameters
Path Parameters
Bucket name.
Responses
Object body
Upload via a signed URL (no bearer token)
List the tenant's buckets
Authorizations
Native data-plane bearer token (lwtk_...) minted at POST /auth/token. Stateless, HMAC-signed under a per-deployment key derived from the at-rest master key; short-lived (default 1h). Revoking the underlying access key invalidates outstanding tokens promptly.
Responses
Bucket list
Create a private bucket
Authorizations
Native data-plane bearer token (lwtk_...) minted at POST /auth/token. Stateless, HMAC-signed under a per-deployment key derived from the at-rest master key; short-lived (default 1h). Revoking the underlying access key invalidates outstanding tokens promptly.
Responses
Bucket created
Get a bucket
Authorizations
Native data-plane bearer token (lwtk_...) minted at POST /auth/token. Stateless, HMAC-signed under a per-deployment key derived from the at-rest master key; short-lived (default 1h). Revoking the underlying access key invalidates outstanding tokens promptly.
Parameters
Path Parameters
Bucket name.
Responses
Bucket
Delete a bucket
Authorizations
Native data-plane bearer token (lwtk_...) minted at POST /auth/token. Stateless, HMAC-signed under a per-deployment key derived from the at-rest master key; short-lived (default 1h). Revoking the underlying access key invalidates outstanding tokens promptly.
Parameters
Path Parameters
Bucket name.
Responses
Deleted
Get bucket versioning state
Authorizations
Native data-plane bearer token (lwtk_...) minted at POST /auth/token. Stateless, HMAC-signed under a per-deployment key derived from the at-rest master key; short-lived (default 1h). Revoking the underlying access key invalidates outstanding tokens promptly.
Parameters
Path Parameters
Bucket name.
Responses
Versioning state
Set bucket versioning state
Authorizations
Native data-plane bearer token (lwtk_...) minted at POST /auth/token. Stateless, HMAC-signed under a per-deployment key derived from the at-rest master key; short-lived (default 1h). Revoking the underlying access key invalidates outstanding tokens promptly.
Parameters
Path Parameters
Bucket name.
Responses
Versioning state
Get the bucket's event-notification configuration
Returns the stored webhook notification configuration WITHOUT the per-config signing secret (only hasSecret acknowledges one exists). Reuses the same notification domain store the S3 GetBucketNotificationConfiguration path reads; an unset config returns an empty configs list (not 404).
Authorizations
Native data-plane bearer token (lwtk_...) minted at POST /auth/token. Stateless, HMAC-signed under a per-deployment key derived from the at-rest master key; short-lived (default 1h). Revoking the underlying access key invalidates outstanding tokens promptly.
Parameters
Path Parameters
Bucket name.
Responses
Notification configuration (secret never returned)
Set the bucket's event-notification configuration
Sets the bucket's webhook notification configuration (webhook target URL, subscribed event types e.g. object-created/object-removed, optional prefix/suffix filters). Validated and stored through the SAME notification validator + bucket-config store the S3 PutBucketNotificationConfiguration path uses, so a config accepted/rejected here is accepted/rejected identically on the S3 path. The per-config signing secret is generated server-side (crypto/rand) and is NEVER returned. An empty configs list clears the configuration. SNS/SQS/Lambda targets are not implemented (501); webhook is the only supported target.
Authorizations
Native data-plane bearer token (lwtk_...) minted at POST /auth/token. Stateless, HMAC-signed under a per-deployment key derived from the at-rest master key; short-lived (default 1h). Revoking the underlying access key invalidates outstanding tokens promptly.
Parameters
Path Parameters
Bucket name.
Responses
Configuration stored or cleared (secret never returned)
Clear the bucket's event-notification configuration
Authorizations
Native data-plane bearer token (lwtk_...) minted at POST /auth/token. Stateless, HMAC-signed under a per-deployment key derived from the at-rest master key; short-lived (default 1h). Revoking the underlying access key invalidates outstanding tokens promptly.
Parameters
Path Parameters
Bucket name.
Responses
Cleared
List objects (prefix/delimiter/continuation pagination)
Authorizations
Native data-plane bearer token (lwtk_...) minted at POST /auth/token. Stateless, HMAC-signed under a per-deployment key derived from the at-rest master key; short-lived (default 1h). Revoking the underlying access key invalidates outstanding tokens promptly.
Parameters
Path Parameters
Bucket name.
Responses
Object listing
Batch delete objects
Authorizations
Native data-plane bearer token (lwtk_...) minted at POST /auth/token. Stateless, HMAC-signed under a per-deployment key derived from the at-rest master key; short-lived (default 1h). Revoking the underlying access key invalidates outstanding tokens promptly.
Parameters
Path Parameters
Bucket name.
Responses
Per-key delete results
List object versions and delete markers (keyMarker/versionIdMarker pagination)
Authorizations
Native data-plane bearer token (lwtk_...) minted at POST /auth/token. Stateless, HMAC-signed under a per-deployment key derived from the at-rest master key; short-lived (default 1h). Revoking the underlying access key invalidates outstanding tokens promptly.
Parameters
Path Parameters
Bucket name.
Responses
Version listing
Server-side copy an object (same-tenant)
Destination is the path bucket/key; the source is the native body {sourceBucket, sourceKey, sourceVersionId?, metadataDirective?, contentType?, metadata?, ifMatch?, ifNoneMatch?, ifModifiedSince?, ifUnmodifiedSince?, requireAbsent?, requireMatchEtag?}. Reuses the S3 copy coordinator path; cross-tenant copy is impossible.
Authorizations
Native data-plane bearer token (lwtk_...) minted at POST /auth/token. Stateless, HMAC-signed under a per-deployment key derived from the at-rest master key; short-lived (default 1h). Revoking the underlying access key invalidates outstanding tokens promptly.
Parameters
Path Parameters
Bucket name.
Responses
Copied
Download an object (Range supported)
Authorizations
Native data-plane bearer token (lwtk_...) minted at POST /auth/token. Stateless, HMAC-signed under a per-deployment key derived from the at-rest master key; short-lived (default 1h). Revoking the underlying access key invalidates outstanding tokens promptly.
Parameters
Path Parameters
Bucket name.
Responses
Object body
Upload an object (streaming, conditional, idempotent, server-side checksum)
An Idempotency-Key header makes the PUT replay-safe and REQUIRES at least one X-Lockwell-Checksum-<alg> header: the key is bound to the body digest plus content length and user metadata, so a same-key replay with a different body is rejected. Idempotency-Key with no checksum fails with 400.
Authorizations
Native data-plane bearer token (lwtk_...) minted at POST /auth/token. Stateless, HMAC-signed under a per-deployment key derived from the at-rest master key; short-lived (default 1h). Revoking the underlying access key invalidates outstanding tokens promptly.
Parameters
Path Parameters
Bucket name.
Responses
Stored
Delete an object
Authorizations
Native data-plane bearer token (lwtk_...) minted at POST /auth/token. Stateless, HMAC-signed under a per-deployment key derived from the at-rest master key; short-lived (default 1h). Revoking the underlying access key invalidates outstanding tokens promptly.
Parameters
Path Parameters
Bucket name.
Responses
Delete marker created
Object metadata only
Authorizations
Native data-plane bearer token (lwtk_...) minted at POST /auth/token. Stateless, HMAC-signed under a per-deployment key derived from the at-rest master key; short-lived (default 1h). Revoking the underlying access key invalidates outstanding tokens promptly.
Parameters
Path Parameters
Bucket name.
Responses
Metadata headers
Get an object's tag set
Authorizations
Native data-plane bearer token (lwtk_...) minted at POST /auth/token. Stateless, HMAC-signed under a per-deployment key derived from the at-rest master key; short-lived (default 1h). Revoking the underlying access key invalidates outstanding tokens promptly.
Parameters
Path Parameters
Bucket name.
Responses
Tag set
Replace an object's tag set
Authorizations
Native data-plane bearer token (lwtk_...) minted at POST /auth/token. Stateless, HMAC-signed under a per-deployment key derived from the at-rest master key; short-lived (default 1h). Revoking the underlying access key invalidates outstanding tokens promptly.
Parameters
Path Parameters
Bucket name.
Responses
Tag set stored
Delete an object's tag set
Authorizations
Native data-plane bearer token (lwtk_...) minted at POST /auth/token. Stateless, HMAC-signed under a per-deployment key derived from the at-rest master key; short-lived (default 1h). Revoking the underlying access key invalidates outstanding tokens promptly.
Parameters
Path Parameters
Bucket name.
Responses
Deleted
Get an object version's retention
Authorizations
Native data-plane bearer token (lwtk_...) minted at POST /auth/token. Stateless, HMAC-signed under a per-deployment key derived from the at-rest master key; short-lived (default 1h). Revoking the underlying access key invalidates outstanding tokens promptly.
Parameters
Path Parameters
Bucket name.
Responses
Retention (mode + retainUntil)
Set an object version's retention (GOVERNANCE or COMPLIANCE)
Reuses the same WORM gate as S3 PutObjectRetention. There is no governance bypass on the native path (a non-goal).
Authorizations
Native data-plane bearer token (lwtk_...) minted at POST /auth/token. Stateless, HMAC-signed under a per-deployment key derived from the at-rest master key; short-lived (default 1h). Revoking the underlying access key invalidates outstanding tokens promptly.
Parameters
Path Parameters
Bucket name.
Responses
Retention set
Get an object version's legal-hold status
Authorizations
Native data-plane bearer token (lwtk_...) minted at POST /auth/token. Stateless, HMAC-signed under a per-deployment key derived from the at-rest master key; short-lived (default 1h). Revoking the underlying access key invalidates outstanding tokens promptly.
Parameters
Path Parameters
Bucket name.
Responses
Legal-hold status (ON/OFF)
Set an object version's legal-hold status (ON/OFF)
Authorizations
Native data-plane bearer token (lwtk_...) minted at POST /auth/token. Stateless, HMAC-signed under a per-deployment key derived from the at-rest master key; short-lived (default 1h). Revoking the underlying access key invalidates outstanding tokens promptly.
Parameters
Path Parameters
Bucket name.
Responses
Legal-hold set
List in-progress multipart uploads
Authorizations
Native data-plane bearer token (lwtk_...) minted at POST /auth/token. Stateless, HMAC-signed under a per-deployment key derived from the at-rest master key; short-lived (default 1h). Revoking the underlying access key invalidates outstanding tokens promptly.
Parameters
Path Parameters
Bucket name.
Responses
Upload list
Create a multipart upload
Authorizations
Native data-plane bearer token (lwtk_...) minted at POST /auth/token. Stateless, HMAC-signed under a per-deployment key derived from the at-rest master key; short-lived (default 1h). Revoking the underlying access key invalidates outstanding tokens promptly.
Parameters
Path Parameters
Bucket name.
Responses
Upload created
Upload a part
Authorizations
Native data-plane bearer token (lwtk_...) minted at POST /auth/token. Stateless, HMAC-signed under a per-deployment key derived from the at-rest master key; short-lived (default 1h). Revoking the underlying access key invalidates outstanding tokens promptly.
Parameters
Path Parameters
Bucket name.
Multipart upload id.
Multipart part number (1-based).
Responses
Part stored
List uploaded parts
Authorizations
Native data-plane bearer token (lwtk_...) minted at POST /auth/token. Stateless, HMAC-signed under a per-deployment key derived from the at-rest master key; short-lived (default 1h). Revoking the underlying access key invalidates outstanding tokens promptly.
Parameters
Path Parameters
Bucket name.
Multipart upload id.
Responses
Part list
Complete a multipart upload
Authorizations
Native data-plane bearer token (lwtk_...) minted at POST /auth/token. Stateless, HMAC-signed under a per-deployment key derived from the at-rest master key; short-lived (default 1h). Revoking the underlying access key invalidates outstanding tokens promptly.
Parameters
Path Parameters
Bucket name.
Multipart upload id.
Responses
Object assembled
Abort a multipart upload
Authorizations
Native data-plane bearer token (lwtk_...) minted at POST /auth/token. Stateless, HMAC-signed under a per-deployment key derived from the at-rest master key; short-lived (default 1h). Revoking the underlying access key invalidates outstanding tokens promptly.
Parameters
Path Parameters
Bucket name.
Multipart upload id.
Responses
Aborted