SDK · TypeScript
TypeScript SDK
@hubfly/sdk is the official TypeScript client for the Hubfly space platform. It ships full type definitions, has no runtime dependencies, and runs anywhere the fetch API exists.
Install
npm install @hubfly/sdk
# or: bun add @hubfly/sdk
# or: pnpm add @hubfly/sdkThe package ships both ESM and CommonJS builds along with its own type declarations, so there is no matching @types/ package to install.
Creating a client
HubflyClient is the single entry point. Pass a personal access token directly, or leave it out and let the client read HUBFLY_TOKEN from the environment.
import { HubflyClient } from '@hubfly/sdk';
// Explicit configuration
const client = new HubflyClient({
token: 'hf_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef',
baseUrl: 'https://api.hubfly.space', // optional — this is the default
timeout: 30_000, // optional — request timeout in ms, default 30s
});
// Or read the token from HUBFLY_TOKEN
const clientFromEnv = new HubflyClient();| Option | Type | Default | Description |
|---|---|---|---|
token | string | process.env.HUBFLY_TOKEN | Personal access token, sent as a bearer token on every request. |
baseUrl | string | https://api.hubfly.space | Override the API host, for example to test against a staging environment. |
timeout | number | 30000 | Milliseconds before an in-flight request is aborted. |
fetch | typeof fetch | globalThis.fetch | Supply your own fetch implementation for logging, proxying or tests. |
Response shape
Every method resolves to the API envelope rather than the bare resource, so the payload is always under data. Destructuring on the way out keeps call sites readable:
const response = await client.projects.list();
response.data; // Project[]
response.meta; // { requestId, … } — worth logging when reporting a problem
// In practice you will usually destructure:
const { data: projects } = await client.projects.list();Projects and containers
A project is the isolation boundary; containers live inside it. Container methods sit under client.projects.containers because every container call needs a project to scope it.
// List every project on the account
const { data: projects } = await client.projects.list();
// Create a project
const { data: project } = await client.projects.create({
name: 'frontend-service',
region: 'us-east-1',
});
// Set environment variables — values marked isSecret are write-only afterwards
await client.projects.updateEnv(project.id, [
{ key: 'NODE_ENV', value: 'production' },
{ key: 'DATABASE_URL', value: 'postgres://…', isSecret: true },
]);
// Deploy a container into the project
const { data: container } = await client.projects.containers.create({
projectId: project.id,
name: 'web',
image: 'nginx:latest',
ports: [{ container: 80, protocol: 'tcp' }],
});
// Lifecycle operations
await client.projects.containers.restart(project.id, container.id);
// Deleting a project removes everything inside it
await client.projects.delete(project.id);Deletes are not recoverable
projects.delete tears down every container, volume and reservation in the project. There is no undo and no grace period, so guard it behind a confirmation in any tooling you build.Load balancers
A load balancer spreads traffic across container targets. Create the balancer, register targets against it, then attach a domain.
const { data: lb } = await client.projects.loadBalancers.create({
projectId: 'proj_123',
name: 'prod-lb',
algorithm: 'round_robin', // 'round_robin' | 'least_connections' | 'ip_hash'
});
// Weights are relative: a target with weight 200 receives twice the
// traffic of one with weight 100.
await client.projects.loadBalancers.createTarget('proj_123', lb.id, {
targetId: 'container_web_1',
port: 8080,
weight: 100,
});
// A certificate is issued automatically once the domain's DNS resolves here
await client.projects.loadBalancers.createDomain('proj_123', lb.id, {
domainName: 'app.example.com',
sslEnabled: true,
});Networking and firewall
Containers in a project share a private network. These methods read that network's state, issue access keys for it, and manage the ingress firewall in front of public endpoints.
// Current private-network state for the project
const { data: network } = await client.projects.network.getAccess('proj_123');
// Issue an access key, e.g. for a CI runner that needs to reach the network
const { data: key } = await client.projects.network.createAccessKey(
'proj_123',
'ci-deployer',
);
// Firewall rules are replaced wholesale, not merged — send the complete set
await client.projects.network.updateFirewall('proj_123', {
projectId: 'proj_123',
enabled: true,
rules: [
{
id: 'allow-https',
direction: 'inbound',
protocol: 'tcp',
portRange: '443',
cidr: '0.0.0.0/0',
action: 'allow',
},
],
});updateFirewall replaces the whole rule set
Port reservations
Reserve a public port on the edge, then map it to a port inside a container. The two steps are separate so a reservation survives the container behind it being replaced.
const { data: reservation } = await client.projects.ports.reserve('proj_123', {
protocol: 'tcp',
port: 5432,
});
await client.projects.ports.map('proj_123', reservation.reservationId, {
containerId: 'cnt_db',
containerPort: 5432,
});Container registry
Store credentials for private registries so builds can pull from them, and read the vulnerability scans that run against uploaded images.
await client.projects.registry.createCredential('proj_123', {
name: 'ghcr',
registryUrl: 'ghcr.io',
username: 'my-org',
password: process.env.GHCR_TOKEN!,
});
const { data: images } = await client.projects.registry.listImages('proj_123');
const { data: scan } = await client.projects.registry.getScan('proj_123', images[0].imageId);
console.log(scan.status); // 'clean' | 'vulnerabilities_found' | 'scanning' | 'failed'Volumes
A volume is persistent block storage that outlives the container mounted on it. Create one, then reference it when you create or update a container.
const { data: volume } = await client.projects.volumes.create({
projectId: 'proj_123',
name: 'postgres-data',
sizeGb: 50,
});
const { data: volumes } = await client.projects.volumes.list('proj_123');GPU instances
The current TypeScript package exposes account-level GPU instance listing. It does not expose the project GPU provisioning operations; use the API referencefor those routes.
const { data: instances } = await client.gpu.list();
for (const instance of instances) {
console.log(instance.id, instance.status);
}Handling errors
Any non-2xx response throws a HubflyApiError. Transport failures are also normalized to this error: network failures use status 0, and a client timeout uses status 408. Check the error type before reading API-specific fields.
import { HubflyClient, HubflyApiError } from '@hubfly/sdk';
try {
await client.projects.get('proj_does_not_exist');
} catch (error) {
if (error instanceof HubflyApiError) {
error.statusCode; // 404
error.message; // 'Project not found'
error.meta?.requestId; // quote this in support requests when present
error.rawResponse; // original response body, when the server returned one
} else {
throw error; // a bug outside the SDK
}
}Errors and retries covers what each status code means and which failures are worth retrying.
Timeouts
The client-wide timeout applies to every request. The current public method signatures do not accept a per-call AbortSignal; create a client with the timeout appropriate for the workflow instead.
const shortLivedClient = new HubflyClient({
token: process.env.HUBFLY_TOKEN,
timeout: 5_000,
});
const { data } = await shortLivedClient.projects.list();Supported runtimes
The SDK uses only globalThis.fetch and AbortController, so it runs unchanged on:
- Node.js 18 and later
- Bun and Deno
- Cloudflare Workers and other edge runtimes
- Browsers — with the caveat below
Developer sub-accounts
The subaccounts and platformKeys modules manage isolated tenants and their machine credentials. Set subaccountId on a client that uses a parent platform key to target ordinary project and resource calls.
const platform = new HubflyClient({ token: process.env.HUBFLY_PLATFORM_TOKEN });
const { data: account } = await platform.subaccounts.create({
name: 'Customer 1042',
externalRef: 'customer_1042',
});
const tenant = new HubflyClient({
token: process.env.HUBFLY_PLATFORM_TOKEN,
subaccountId: account.id,
});
const { data: projects } = await tenant.projects.list();Read the sub-account API guide before implementing funding, credential rotation, suspension, or closure.
Do not ship a token to the browser