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/sdkNode 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.
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);| Option | Default | What |
|---|---|---|
apiKey | CLOUDHOST_API_KEY | the API key |
baseUrl | CLOUDHOST_API_URL · https://api.ibel.ai | the API address |
env | process.env | in a function, pass the function's env |
maxRetries | 3 | retries after 429 and 503 |
maxRetryDelay | 60000 | the longest wait before a retry, ms; a longer Retry-After throws at once |
timeout | 60000 | ms per attempt |
fetch | the global fetch | your 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.
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 namespaceErrors#
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.
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;
}| Class | Status |
|---|---|
BadRequestError | 400 |
AuthenticationError | 401 |
PaymentRequiredError | 402 |
PermissionDeniedError | 403 |
NotFoundError | 404 |
ConflictError | 409 |
PayloadTooLargeError | 413 |
RateLimitError | 429 |
InternalServerError · BadGatewayError · ServiceUnavailableError | 500 · 502 · 503 |
ConnectionError · TimeoutError | no answer |
ConfigurationError | no 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.
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.
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.
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.
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.