open-sites-client
v0.9.0
Published
Typed API client for an open-sites hub — create, version, deploy and share websites from Node or the browser
Maintainers
Readme
open-sites-client
Typed, dependency-free client for the REST API of an open-sites hub — the self-hostable platform that lets AI agents create, publish, and share websites. Works in Node 18+ and browsers (global fetch).
npm install open-sites-clientimport { OpenSitesClient, OpenSitesError } from "open-sites-client";
const hub = new OpenSitesClient("https://my-hub.example.com", process.env.OPEN_SITES_TOKEN);
const site = await hub.createSite({ name: "Launch page" });
await hub.saveVersion(site.slug, [{ path: "index.html", content: "<h1>Hello</h1>" }], { deploy: true });
console.log(hub.siteUrl(site)); // → https://sites.example.com/s/launch-page/ (whatever the hub reports)
// Change one file without re-uploading the rest
await hub.updateFiles(site.slug, [{ path: "style.css", content: "body{font:16px system-ui}" }], { deploy: true });
// Read the current site in one call
const { files } = await hub.getVersionFiles(site.slug, "latest", { includeContent: true });
// Preview an undeployed version, share it, then deploy
const v = await hub.saveVersion(site.slug, files, { message: "draft" });
const { url } = await hub.previewUrl(site.slug, v.number);
try {
await hub.createSite({ name: "Launch page", slug: site.slug });
} catch (e) {
if (e instanceof OpenSitesError && e.code === "conflict") { /* slug taken */ }
}Every hub error is an OpenSitesError with status, a stable code (validation_failed, bad_request, unauthorized, forbidden, not_found, conflict, payload_too_large, rate_limited, quota_exceeded, internal_error, or network_error) and optional details.
Get a token from the hub's Settings page or with OpenSitesClient.obtainToken(hubUrl, username, password).
Methods
| Area | Methods |
|---|---|
| You | me() (username, displayName, role, quotas — sites/bytes used vs. limit for your personal organization, orgs — the organizations you belong to, hub.functions — whether the hub runs server code, hub.sql — on/off/unavailable) · siteUrl(site) |
| Sites | listSites() · listSitesPage({q, sort, owner, limit, offset}) · createSite({name, slug?, description?, org?}) · getSite(slug) · updateSite(slug, {name?, description?, slug?, owner?}) · deleteSite(slug) |
| Organizations | listOrgs() · createOrg({name, slug?, description?}) · getOrg(slug) · updateOrg(slug, patch) · deleteOrg(slug) · listOrgMembers(slug) · setOrgMembers(slug, members) · getOrgUsage(slug) · listOrgAudit(slug, {limit?, offset?}) |
| Versions | saveVersion(slug, files, {message?, deploy?}) · updateFiles(slug, files, {base?, remove?, message?, deploy?}) · listVersions(slug) · listVersionsPage(slug, {limit, offset}) · getVersionFiles(slug, ref, {includeContent?}) · getVersionFile(slug, ref, path) · diffVersions(slug, from, to, {includePatch?, path?}) · deleteVersion(slug, n) · previewUrl(slug, ref) |
| Deploy & access | deploy(slug, n?) · undeploy(slug) · setAccess(slug, {visibility?, password?, grants?, allowAnonymousWrites?}) |
| Runtime data (editors) | listKv(slug, {scope?, prefix?, limit?, offset?}) · getKv(slug, scope, key) · deleteKv(slug, {scope?, key?}) · listObjects(slug, {prefix?, limit?, offset?}) · downloadObject(slug, key) · deleteObject(slug, key) · purgeObjects(slug) |
| Server functions | listSecrets(slug) (names only) · setSecrets(slug, {NAME: value \| null}) (owner; merge — null deletes) · deleteSecret(slug, name) (owner) · getLogs(slug, {limit?, offset?, since?, kind?, level?, requestId?}) · clearLogs(slug) (owner) |
| Container apps | getBuild(slug, {version?, wait?}) (editors; wait seconds polls until ready/failed) · rebuild(slug, version?) (editors) · getContainer(slug) (editors; runtime status + build + limits) · restartContainer(slug) (owner) |
| Per-site SQL | runSql(slug, query, {params?, maxRows?}) (owner; one statement with params, else a ;-script in one transaction → {results: SqlResult[], statements, durationMs}) · listSqlTables(slug) (editors; {state, mode, quotaBytes, usage, tables: SqlTableInfo[]}) · resetSql(slug) (owner; drops everything in the schema) |
| Observe | getAnalytics(slug, {from?, to?}) · getSiteUsage(slug) (incl. logs.rows, secrets.count) · listSiteAudit(slug, {limit?, offset?}) (owner) |
| Admin | getHubUsage() · listAudit({action?, actor?, target?, since?, limit?, offset?}) · runMaintenance() · gcBlobs() · listUsers({q?}) · createUser({username, password, displayName?, role?}) · createToken(name) |
ref is a version number, "latest" or "deployed". Paged results carry {total, limit, offset, nextOffset}.
Full-stack sites
A version that contains _server/index.js (export default { async fetch(request, env, ctx) }) runs server
code on the hub; SiteSummary.hasServer / VersionSummary.hasServer say so. Secrets the code reads as
env.SECRETS.NAME are managed with setSecrets / listSecrets / deleteSecret (values are never returned —
SiteSecretMeta is name + timestamps), and getLogs returns SiteLogEntry rows (kind: request | log |
error, with requestId matching the x-request-id header of a failed response). SiteLogQuery is the filter type.
runSql returns SqlResult rows exactly as env.SQL.query does inside server code (JSON-safe values: ISO timestamps,
int8/numeric as strings, bytea as base64); a Postgres error is an OpenSitesError with code: "sql_error" and
details.sqlstate / details.position.
Container apps
A version whose root has a Dockerfile has kind: "container" (SiteSummary.kind / VersionSummary.kind; static and
_server/ versions are static / functions). saveVersion on such a version returns build (a SiteBuild:
queued | building | ready | failed, error, logTail, imageBytes, deployOnReady) and deployed: false — the
hub builds asynchronously; getBuild(slug, {version, wait: 180}) polls until it is done. deploy needs a ready image
(OpenSitesError code: "build_pending" | "build_failed"; container_start_failed when the container never opened
$PORT). getContainer reports the deployed container (ContainerStatus), getLogs({kind: "build"}) the build output,
and SiteUsage.containers the images kept. me().hub.containers is "on" | "off" | "unavailable" (ContainersState).
Owners
A site is owned by a person or an organization, and the two share one namespace. SiteSummary.owner
is therefore a username or an organization slug — read ownerKind ("personal" | "team") to tell
them apart, and ownerName for the display name. Every user has a personal organization whose slug is
their username, so on a single-author hub owner reads exactly as it always did. Organization,
OrgDetail, OrgMember and OrgRole ("admin" | "member" | "viewer") are exported.
Upgrading from 0.4: HubUsage.perUser is now HubUsage.perOwner, one entry per owning
organization ({slug, name, kind, sites, storedBytes, kvBytes}), and quota_exceeded details name
sitesPerOrg / bytesPerOrg instead of sitesPerUser / bytesPerUser.
Full API reference: src/index.ts. For AI agents, use the open-sites-mcp server instead. MIT.
