Store API

S3-backed object storage with org-scoped buckets, presigned URLs for direct browser uploads, and cursor-based pagination.

Buckets

Buckets are org-scoped containers. Each org can create multiple buckets to organize objects by purpose (uploads, avatars, exports, etc.).

MethodPathDescription
GET /v1/buckets List all buckets for the org
POST /v1/buckets Create a new bucket

SDK Examples

// List buckets
const buckets = await client.store.listBuckets();

// Create a bucket
const { bucket, created } = await client.store.createBucket({
  name: 'user-uploads',
});
// Rust SDK
use kapable_sdk::store::types::*;

let buckets = client.store().list_buckets().await?;

let resp = client.store().create_bucket(CreateBucketRequest {
    name: "user-uploads".into(),
}).await?;

Objects

Objects are files stored in buckets. Upload, download, list, delete, and inspect objects via the Objects endpoints.

MethodPathDescription
GET /v1/objects/{bucket} List objects in a bucket (prefix filter, cursor pagination)
PUT /v1/objects/{bucket}/{key} Upload an object (raw bytes body)
GET /v1/objects/{bucket}/{key} Download an object
DELETE /v1/objects/{bucket}/{key} Delete an object
HEAD /v1/objects/{bucket}/{key} Get object metadata (size, content-type, etag)

SDK Examples

// Upload a file
const data = new TextEncoder().encode('Hello, world!');
const uploaded = await client.store.uploadObject(
  'user-uploads',
  'docs/readme.txt',
  data,
  'text/plain',
);
console.log(`Uploaded: ${uploaded.key}, size: ${uploaded.size}`);

// Download a file
const response = await client.store.downloadObject(
  'user-uploads',
  'docs/readme.txt',
);
const text = await response.text();

// List objects with prefix
const listing = await client.store.listObjects('user-uploads', {
  prefix: 'docs/',
  max_keys: 100,
});

// Auto-paginate through all objects
for await (const obj of client.store.paginateObjects('user-uploads')) {
  console.log(obj.key, obj.size);
}

// Delete an object
await client.store.deleteObject('user-uploads', 'docs/readme.txt');
// Rust SDK
let data = b"Hello, world!".to_vec();
let uploaded = client.store().upload_object(
    "user-uploads", "docs/readme.txt", data, Some("text/plain"),
).await?;

let bytes = client.store().download_object(
    "user-uploads", "docs/readme.txt",
).await?;

let listing = client.store().list_objects("user-uploads", ListObjectsParams {
    prefix: Some("docs/".into()),
    max_keys: Some(100),
    ..Default::default()
}).await?;

Presigned URLs

Generate presigned S3 URLs for direct browser uploads and downloads. This avoids proxying large files through the API server.

MethodPathDescription
POST /v1/presign/{bucket}/{key} Generate a presigned upload URL
GET /v1/presign/{bucket}/{key} Generate a presigned download URL

SDK Examples

// Get a presigned upload URL (browser can PUT directly to S3)
const { url, expires_at } = await client.store.presignUpload(
  'user-uploads',
  'photos/avatar.jpg',
  { expires_in: 3600 }, // 1 hour
);

// Browser-side upload using the presigned URL
await fetch(url, {
  method: 'PUT',
  body: fileBlob,
  headers: { 'Content-Type': 'image/jpeg' },
});

// Get a presigned download URL
const download = await client.store.presignDownload(
  'user-uploads',
  'photos/avatar.jpg',
);
// Redirect user to download.url
External vs Internal S3 Endpoint

The store service uses a separate S3 client for presigning that targets the external endpoint (S3_EXTERNAL_ENDPOINT). This ensures the presigned URL's signature matches the host the browser will connect to.