DEV Community

octave Nkurunziza
octave Nkurunziza

Posted on Originally published at blog.hubfly.space

Isolated SaaS tenants on Hubfly: 1 wallet and 1 key per customer in 4 API calls

Originally published on the Hubfly blog.

TL;DR

  • What you get: a pattern for giving each customer their own wallet, projects and machine key on Hubfly, using sub-accounts. Three control-plane calls per customer, then the normal API.
  • Who it's for: SaaS teams, agencies and AI-agent builders who run infrastructure for their customers.
  • Time: about 10 minutes to read. Creating, suspending and revoking a tenant in the dashboard takes about 2 minutes.

The problem: one customer's bill is everyone's bill

You run a SaaS. Each customer gets a database, a worker or a preview app. On most clouds, all of that lands in one account and one bill.

Then customer 1042's import job loops all weekend. You find out from your card statement. You also can't tell which customer owes what, and one leaked key reaches every tenant.

The textbook answer is the "silo" model: a separate account per customer. On AWS this usually means AWS Organizations and an account-vending process. Most guides stop at the diagram. This post shows the actual calls on Hubfly, and the limits.

What Hubfly sub-accounts are

A sub-account is a tenant, not a team member. It has no password, no login and no invitations. Your software talks to it with a machine key (docs).

Each tenant gets:

  • Its own wallet, with paid and promotional balances, in integer micro-USD (1000000 = $1.00).
  • Its own projects, which are ordinary Hubfly projects: containers, Compose, volumes, domains, firewall, logs.
  • Its own keys. An hfsk_ key is locked to one tenant. A parent hfpk_ key can manage all your tenants and target one with the X-HubFly-Subaccount header.
  • An externalRef, unique inside your parent, so you can map customer_1042 in your database to sub_… in Hubfly.

The docs list the use cases plainly: SaaS customer infrastructure, AI agents, managed hosting, preview environments and internal developer portals.

Your app (parent hfpk_ key)
 ├── sub_A  wallet $25 ── project ── containers, volumes, domains
 ├── sub_B  wallet $10 ── project ── ...
 └── sub_C  suspended  (keys disabled)
Enter fullscreen mode Exit fullscreen mode

Walkthrough: the dashboard flow

Open Profile → Sub-accounts.

The Create sub-account dialog with Account name and External reference fields

  1. Create. Click New sub-account, type a name such as acme-corp and an external reference such as tenant_001, and click Create sub-account. It shows as Active with a sub_… ID within seconds.
  2. Inspect. Manage opens a panel with the wallet balance ($0.00), active keys, status, a Fund wallet box ("Move paid balance from the parent account"), Machine credentials and Suspend account.

Tenant panel: wallet $0.00, 0 active keys, status Active

  1. Key (the surprise). New key has no form. The dashboard immediately creates a key called Master key and shows the hfsk_ secret once, with "HubFly stores only a hash of this key and cannot recover it later." If you don't need it, close the dialog and click Revoke. The banner says "API key revoked." and active keys go back to 0. Note that Revoke also has no confirmation step.

Banner

  1. Suspend. Suspend account works in one click, again with no confirmation. Status changes to Suspended, New key greys out, and the button becomes Resume account.

Tenant status Suspended with a Resume account button

Everything above costs $0.00. Funding the wallet and closing a tenant happen through the API below; the dashboard has no Close button, so a tenant you stop using stays suspended until you close it by API.

The production flow: 4 calls to onboard, 1 to close

The dashboard is fine for a look around. For real customers, provision from your backend with a parent platform key. These come from the Sub-account API guide; all paths are under https://api.hubfly.space/api/v1.

1. Create the tenant. Use an idempotency key, so a retry returns the same tenant instead of a duplicate.

curl -X POST https://api.hubfly.space/api/v1/subaccounts/create \
  -H "Authorization: Bearer $HUBFLY_PLATFORM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: tenant-customer-1042-v1" \
  -d '{"name":"Customer 1042","externalRef":"customer_1042"}'
Enter fullscreen mode Exit fullscreen mode

2. Fund its wallet. This moves paid balance from your parent wallet. Promotional credit can't be transferred.

curl -X POST https://api.hubfly.space/api/v1/subaccounts/$SUBACCOUNT_ID/wallet/transfers \
  -H "Authorization: Bearer $HUBFLY_PLATFORM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: funding-customer-1042-2026-10" \
  -d '{"amountMicroUsd":"25000000","idempotencyKey":"funding-customer-1042-2026-10"}'
Enter fullscreen mode Exit fullscreen mode

3. Mint a tenant key and write it straight to your secret manager. It's shown once.

curl -X POST https://api.hubfly.space/api/v1/subaccounts/$SUBACCOUNT_ID/api-keys/create \
  -H "Authorization: Bearer $HUBFLY_PLATFORM_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"production workload"}'
Enter fullscreen mode Exit fullscreen mode

4. Use the normal endpoints. There are no special tenant URLs. With the tenant key, GET /projects lists only that tenant's projects. With your platform key, add -H "X-HubFly-Subaccount: $SUBACCOUNT_ID". The TypeScript and Go SDKs and the CLI (--subaccount sub_… or HUBFLY_SUBACCOUNT) support the same pattern.

5. Close when the customer leaves. Stop and remove the tenant's projects, settle any negative balance, then call POST /subaccounts/$SUBACCOUNT_ID/close. Eligible paid balance returns to your parent wallet; promotional balance expires; keys are revoked.

Isolation rules: an hfsk_ key sent with a different tenant header gets 403 SUBACCOUNT_SCOPE, and a platform key can't target a tenant outside its parent.

Limits and pricing (honest version)

Limits:

  • 25 active sub-accounts per parent and 10 active keys per parent or tenant, by default.
  • One level deep: a tenant can't create tenants. You can't move a tenant to another parent.
  • No GPU, no GitHub OAuth repo discovery, no team members or invitations, no payment methods inside a tenant.
  • No automatic top-up for tenant wallets in the first release.
  • Suspending doesn't stop running resources. The docs say to stop billable resources before you suspend.
  • Closing needs no active projects and no negative balance. Force close isn't available yet.

Pricing: sub-accounts have no listed fee. A tenant pays normal Hubfly rates from its own wallet: $0.004 per vCPU-hour, $0.002 per GB-hour of RAM, $0.00001536 per GB-hour of Standard storage.

Per tenant, always on (730 h) Math Monthly
1 vCPU, 0.5 GB RAM, 5 GB disk 2.92 + 0.73 + 0.06 $3.71
0.5 vCPU, 0.5 GB RAM, 5 GB disk 1.46 + 0.73 + 0.06 $2.25
25 tenants on the first size 25 × $3.706 $92.65

Whether each tenant gets its own 360 free hours isn't documented, so don't price your plans on it. Per the pricing page, sleeping containers pause compute billing; volumes are still billed.

What can go wrong

  1. You mint keys by accident. One click on New key creates a key named Master key. Create keys from your backend, name them per workload, and revoke old ones after rotation.
  2. You suspend a tenant and it keeps billing. Suspension disables credentials and changes, not running containers. Stop the tenant's workloads first, then suspend.
  3. A customer passes someone else's tenant ID. Never accept a subaccountId from the client. Resolve it from your own customer record, and alert on 403 SUBACCOUNT_SCOPE.
  4. 409 API_KEY_LIMIT_REACHED. A tenant holds 10 active keys by default. Revoke unused keys before creating another.

Bonus: an ambiguous funding failure. Don't retry with a new idempotency key until you've read the wallet; you could fund twice.

Who this is NOT for

  • Your own team. For colleagues, use an organization with roles. Sub-accounts have no logins.
  • GPU workloads per customer. Not supported in tenants today.
  • More than 25 customers, today, with no plan. That's the default cap. Talk to us first if you need more.
  • Customers who must hold their own bill. Tenants have no payment methods; you fund them from your paid balance.

Takeaway

If you sell software that runs on infrastructure, sub-accounts give you the silo model without account vending: one wallet, one project namespace and one revocable key per customer. Provision from your backend with idempotency keys, keep keys out of the dashboard, and stop workloads before you suspend.

Read next: Let your coding agent deploy to Hubfly safely (give each agent its own tenant), Gate your app on a Postgres migration job and 502 Bad Gateway in Docker? Bind to 0.0.0.0.

Building a platform on Hubfly? Talk to sales about tenant limits, or start free with 360 hours included and create your first sub-account in Profile → Sub-accounts.

Top comments (0)