Skip to content
cloudhostDocs
Get started

The TypeScript SDK

The whole API in one typed client. The SDK is generated from the API description, so every route, parameter and answer is the one in the API reference, and it uses only web-standard APIs, so it runs the same in Node, browsers and cloudhost functions.

Install#

npm install @cloudhost/sdk

Node 18 or newer, any current browser, and cloudhost functions. No dependencies.

Start#

Create an API key in the dashboard (Settings, API keys) or with cloudhost keys create. The client reads it from CLOUDHOST_API_KEY, or pass it to the constructor.

Start
import { Cloudhost } from "@cloudhost/sdk";

// the key from CLOUDHOST_API_KEY, or: new Cloudhost({ apiKey: "chk_live_..." })
const client = new Cloudhost();

const me = await client.account.whoami();
console.log(me.team, me.role);
OptionDefaultWhat
apiKeyCLOUDHOST_API_KEYthe API key
baseUrlCLOUDHOST_API_URL · https://api.ibel.aithe API address
envprocess.envin a function, pass the function's env
maxRetries3retries after 429 and 503
maxRetryDelay60000the longest wait before a retry, ms; a longer Retry-After throws at once
timeout60000ms per attempt
fetchthe global fetchyour own (tests, a proxy)

How it is shaped#

There is a namespace per part of the API: functions, apps, databases, caches, storage, queues, flags, domains, billing, teams, account and the rest. Path parameters come first, then the JSON body, then the query parameters, and last the request options (signal, timeout, maxRetries, headers). Field names are the API's own (snake_case), so the API reference reads the same.

Example
import { Cloudhost } from "@cloudhost/sdk";
const client = new Cloudhost();

await client.functions.get("my-api"); // GET /v1/functions/my-api
await client.functions.deploy("my-api", { version: 12 }); // a JSON body
await client.functions.getLogs("my-api", { since: "1h", level: "error" }); // query parameters
await client.flags.functions.list("my-api"); // a product's part of another namespace

Errors#

Every error answer is an ApiError subclass chosen by its HTTP status, with the API's stable code (for example not_found), the message and details (the code's extra fields). code is typed: every code the API documents.

Example
import { Cloudhost, ApiError, ConflictError, RateLimitError } from "@cloudhost/sdk";
const client = new Cloudhost();

try {
  await client.databases.create({ name: "shop" });
} catch (err) {
  if (err instanceof ConflictError && err.is("name_taken")) console.log("the name is taken");
  else if (err instanceof RateLimitError) console.log("try again in", err.retryAfter, "s");
  else if (err instanceof ApiError) console.log(err.status, err.code, err.message);
  else throw err;
}
ClassStatus
BadRequestError400
AuthenticationError401
PaymentRequiredError402
PermissionDeniedError403
NotFoundError404
ConflictError409
PayloadTooLargeError413
RateLimitError429
InternalServerError · BadGatewayError · ServiceUnavailableError500 · 502 · 503
ConnectionError · TimeoutErrorno answer
ConfigurationErrorno API key, before any request

Retries#

A 429 (rate limited) or 503 (unavailable for a moment) is sent again up to maxRetries times. The wait is the answer's Retry-After, else 0.5, 1, 2 ... up to 8 seconds with jitter. A lost connection is retried on reads only, since a write may already have happened; a request whose body is a stream is never sent twice.

Example
import { Cloudhost } from "@cloudhost/sdk";
const client = new Cloudhost({ maxRetries: 5, timeout: 30_000 });
client.transport.onRetry = ({ attempt, delayMs, error }) => console.warn("retry", attempt, "in", delayMs, "ms:", error.message);

// per call: a signal, a timeout, retries, headers
const ctl = new AbortController();
await client.functions.list({ signal: ctl.signal, maxRetries: 0 });

Paging#

A list that pages has an ...All() twin: iterate every item with for await, the pages with .pages(), or stop early with .toArray({ max }). Logs, the audit log, notifications, webhook deliveries, traces, queue messages, support cases and flag history.

Example
import { Cloudhost } from "@cloudhost/sdk";
const client = new Cloudhost();

for await (const event of client.account.listAuditAll({ type: "deploy" })) {
  console.log(event.at, event.action);
}
const errors = await client.functions.getLogsAll("my-api", { since: "24h", level: "error" }).toArray({ max: 500 });
for await (const page of client.support.listAll({ status: "active" }).pages()) console.log(page.total);

Every product#

import { Cloudhost } from "@cloudhost/sdk";
const client = new Cloudhost();

await client.functions.create({ name: "my-api" });
const v = await client.functions.uploadVersion("my-api", {
  main_module: "index.js",
  modules: { "index.js": `export default { fetch: () => new Response("hello") }` },
});
const live = await client.functions.deploy("my-api", { version: v.version });
await client.functions.putSecret("my-api", "PAYMENT_KEY", { value: "..." });

const tail = await client.functions.tail("my-api"); // the live log
for await (const entry of tail) {
  console.log(entry.level, entry.msg);
  if (entry.level === "error") break;
}

Domains, builds, previews, alerts, webhooks, teams and billing have the same shape; the package README has an example of each.

In a function#

Make the key a sensitive environment variable of the function and pass the function's env to the client.

Function
import { Cloudhost } from "@cloudhost/sdk";

export default {
  async fetch(request: Request, env: Record<string, string>) {
    const client = new Cloudhost({ env }); // reads CLOUDHOST_API_KEY from the function's env
    const { functions } = await client.functions.list();
    return Response.json(functions.map((f) => f.name));
  },
};

In a browser#

The package has no Node-only code and works with any bundler.

An API key in a web page can be read by anyone who opens it. Call the API from your server or a function, or give the page a key with the narrowest role (viewer) and a short expiry. The API answers calls from other web origins only on its public routes for now, so a page on another origin calls through your own server.

Versions#

Each SDK version is generated for one API version and sends it in the cloudhost-version header. New API routes arrive in the next SDK version; until then client.transport.request() calls any route.