Hubfly spaceDocs
Console

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/sdk

The 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.

TypeScript
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();
OptionTypeDefaultDescription
tokenstringprocess.env.HUBFLY_TOKENPersonal access token, sent as a bearer token on every request.
baseUrlstringhttps://api.hubfly.spaceOverride the API host, for example to test against a staging environment.
timeoutnumber30000Milliseconds before an in-flight request is aborted.
fetchtypeof fetchglobalThis.fetchSupply 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:

TypeScript
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.

TypeScript
// 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.

TypeScript
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.

TypeScript
// 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

The rules you send become the complete configuration. Read the current rules and append to them if you only mean to add one, otherwise you will silently drop the rest.

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.

TypeScript
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.

TypeScript
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.

TypeScript
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.

TypeScript
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.

TypeScript
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.

TypeScript
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.

TypeScript
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

Anything in front-end JavaScript is readable by anyone who opens devtools. Call the SDK from a server, an edge function or a background job, and expose only the narrow operations your front end actually needs.
Something unclear or out of date? Emailsupport@hubfly.spaceBack to top