/// FILE NO. KS—DOCS

Install. Quickstart. Reference.

Get the CLI on your machine, the SDK in your project, and the API in your head, in that order.

/// INSTALL

Two paths in.

The CLI for daily uploads, scripting, and ingest. The SDK when you're writing code against the API.

FRAME 01 · CLI

Command-line client.

curl -fsSL https://khaosstorage.com/install | sh

Drops khaos-storage and the khs alias on your PATH. Re-run to upgrade. Requires Node 20+.

FRAME 02 · SDK

TypeScript client.

npm i https://khaosstorage.com/sdk/latest.tgz

Same package we'll publish to npm later. Works in Node and any modern bundler.

/// QUICKSTART

Three steps to first asset.

Sign in, upload, share. The CLI version is one terminal pane; the Node version is six lines of code.

01 Sign in

Authorize once, in the browser.

khs login
02 Upload

Pick files interactively.

khs upload
03 Share

Publish into a space.

khs upload showreel.mov \
  --space "Highlights"
/// API REFERENCE · v0.1

The shape of the API.

Every endpoint takes Authorization: Bearer <key>, returns { data, meta?, error? }, and is gated by a scope on the calling key. Errors carry a code, message, and (when applicable) a fields array.

Base URL

https://api.khaosstorage.com

Scopes

read · write · admin. Higher scopes inherit lower.

Errors

HTTP status, plus error.code like insufficient_scope, validation_error, not_found.

/// ASSETS

Assets

Upload, list, get, update, delete, and resolve signed download URLs. The upload helpers in the SDK and CLI choreograph this resource end to end.

POST /v1/assets write

Create an asset and receive an upload descriptor (single PUT for ≤100 MiB, multipart above). The SDK's client.upload(...) wraps this plus the part PUT and complete calls.

khs upload clip.mov
GET /v1/assets read

List assets. Supports limit, cursor, status, type, search, and modified_after.

Node

const assets = await client.assets.list({
  type: 'video',
  status: 'active',
  limit: 50,
});
GET /v1/assets/{asset_id} read

Fetch one asset by id. The response carries any populated thumb_url and preview_url as 1-hour signed GETs.

await client.assets.get('ast_…');
PATCH /v1/assets/{asset_id} write

Update tags, filename, or attributes. Returns the full asset.

await client.assets.patch('ast_…', { tags: ['raw','drone'] });
DELETE /v1/assets/{asset_id} write

Hard-delete the asset and its R2 objects (original, thumb, preview). Idempotent.

await client.assets.delete('ast_…');
GET /v1/assets/{asset_id}/url read

Mint a signed download URL for the original. Default 1-hour TTL via expires_in.

await client.assets.url('ast_…', 3600);
POST /v1/assets/{asset_id}/parts write

Mint a per-attempt presigned PUT URL for one part of a multipart upload. Refresh per attempt; URLs expire in 15 minutes.

await client.assets.presignPart('ast_…', uploadId, partNumber);
POST /v1/assets/{asset_id}/complete write

Finalize the upload. Pass upload_id + parts for multipart; empty body for single PUT. Triggers the processor fan-out.

await client.assets.completeUpload('ast_…', uploadId, parts);
POST /v1/assets/{asset_id}/abort write

Abort an in-flight multipart upload. Cleans up R2 state. Called automatically by the SDK when an upload is cancelled.

await client.assets.abortUpload('ast_…', uploadId);
POST /v1/assets/{asset_id}/reprocess write

Re-run the image / video processor. Useful after a processor bug fix or to regenerate thumbnails.

await client.assets.reprocess('ast_…');
GET /v1/assets/{asset_id}/spaces read

List the spaces an asset is currently published into.

await client.assets.spaces('ast_…');
/// SPACES

Spaces

A space is how you share. Public = anyone with the URL. Protected = anyone with the URL plus a password. Assets are always private; publishing into a space is what surfaces them externally.

POST /v1/spaces admin

Create a space. visibility is public or protected; the latter requires a password.

await client.spaces.create({
  name: 'Highlights',
  visibility: 'public',
});
GET /v1/spaces read

List your spaces.

await client.spaces.list();
GET /v1/spaces/{space_id} read

Get one space, including asset_count.

await client.spaces.get('spc_…');
PATCH /v1/spaces/{space_id} admin

Update name, description, visibility, or password. Pass password: null to clear.

await client.spaces.update('spc_…', { visibility: 'public', password: null });
DELETE /v1/spaces/{space_id} admin

Delete the space. Cascades publications. Underlying assets are not affected.

await client.spaces.delete('spc_…');
POST /v1/spaces/{space_id}/publications write

Publish one or more assets into the space. Returns per-asset success / error.

await client.spaces.publish('spc_…', ['ast_…','ast_…']);
GET /v1/spaces/{space_id}/publications read

List the assets published into a space, decorated with signed thumb/preview URLs.

await client.spaces.listPublications('spc_…');
DELETE /v1/spaces/{space_id}/publications/{asset_id} write

Unpublish one asset from a space. The asset itself stays.

await client.spaces.unpublish('spc_…','ast_…');
/// API KEYS

API keys

Long-lived bearer tokens for scripts, ingest agents, and integrations. Created with a scope (read / write / admin). The raw key is shown once at creation.

GET /v1/api-keys read

List your keys. Hashes only; never the raw value.

await client.apiKeys.list();
POST /v1/api-keys admin

Mint a new key. The response includes the raw key exactly once. Persist it now.

await client.apiKeys.create({
  name: 'ingest-bot',
  scopes: ['write'],
});
DELETE /v1/api-keys/{key_id} admin

Revoke. Idempotent. Subsequent requests bearing the revoked key fail at the authorizer.

await client.apiKeys.revoke('key_…');
/// STORAGE CONNECTIONS

Storage connections

Bring your own R2 or S3-compatible storage to extend capacity. Credentials live encrypted at rest under a per-account KMS key; nothing is logged or replayable.

GET /v1/storage-connections read

List configured connections.

POST /v1/storage-connections admin

Add a connection. The server runs HEAD / LIST / PUT / DELETE probes against the bucket before persisting.

POST /v1/storage-connections/preview write

Run the same probes without persisting. Useful for the onboarding wizard.

POST /v1/storage-connections/list-buckets write

Discovery: enumerate buckets visible to the credentials. Used during connection setup.

POST /v1/storage-connections/create-bucket admin

Discovery: create a new bucket using the credentials. Region-aware.

/// ACCOUNT

Account

The account record for the calling owner.

GET /v1/account read

Read the current account: display name, email, plan, status.

PATCH /v1/account admin

Update display name or email.

/// USAGE

Usage

Plan utilization. Bytes and counts, split by Khaos-managed vs BYOK.

GET /v1/usage read

Returns total_assets, total_bytes, and the same broken out by managed_* and byok_*.

/// PUBLIC SPACES

Public spaces

Unauthenticated. The viewer endpoints used by share links. Public spaces resolve immediately; protected spaces require a password unlock.

GET /v1/public/spaces/{short_id} none

If the space is public, returns the manifest with signed asset URLs. If protected, returns { requires_password: true }.

POST /v1/public/spaces/{short_id}/access none

Submit { password }; on match, returns the manifest. No tokens, no sessions; re-auth per request.

/// OPENAPI

The spec.

A live openapi.yaml is in the repository. Drop it into a Postman / Bruno / Insomnia / Stoplight collection and you have a request playground.

openapi.yaml is being brought current with v0.1 — link added once published.

/// SUPPORT

Get in touch.

Found a bug, want a feature, need access. We read everything.

Email k@khaos.studio.