Waymaker API
WaymakerAPI
Developer Documentation
Documentation

Storage — files your app keeps

A bucket is a place your app on Host keeps files: user avatars, uploaded documents, generated PDFs, product images. You never hold an access key, and the bytes never pass through us on their way in or out.

Files in Commander are for people — documents, in a folder, in a project. Storage is for your app. Same object store, same allowance, different audience.

Make a bucket

waymaker storage create customer-uploads

Private by default, and private means private. Being in the organisation gets you nothing: you see a bucket's contents only if someone added you to that bucket. The person who creates it is its first manager, and nobody else is added.

That is stricter than Commander Files on purpose. A project file is shared work — the project is the boundary. A bucket holds your application's data: uploads belonging to your customers, not to your colleagues. That default is impossible to tighten later once people rely on it.

To make one public:

waymaker storage create product-images --public

Anyone with the URL can then read every object in it, forever, without signing in. Writing still requires membership.

Put a file in, get one out

waymaker storage put customer-uploads ./invoice.pdf invoices/2026/inv-1.pdf
waymaker storage ls  customer-uploads --prefix invoices/2026/
waymaker storage url customer-uploads invoices/2026/inv-1.pdf --expires 900
waymaker storage rm  customer-uploads invoices/2026/inv-1.pdf

url gives you a link that works for as long as you asked for and then stops. That is how you show a private file to a browser without making the bucket public.

Uploads happen in three steps, and it matters why. Space is reserved before any bytes move, so an upload can never take your organisation past its allowance halfway through and leave a half-written file behind. Then the bytes go straight to the store — nothing proxies them. Then the upload is confirmed, and the size is read from the store rather than taken from whatever the client claimed. waymaker storage put does all three for you.

Who can see it

waymaker storage member add customer-uploads user_abc --role writer
waymaker storage member remove customer-uploads user_abc
RoleCan
readerList and read objects
writerAlso upload and delete
managerAlso add and remove people, change visibility, delete the bucket

Two rules that will otherwise surprise you:

Removing the last manager is refused. A bucket nobody manages can never be administered again — and an organisation admin deliberately has no way to reach in and fix it. Add a second manager first.

Admins see that a bucket exists, not what is in it. They are accountable for the bill, so they can always see a bucket's size, its members and its settings. Reading the objects requires membership, like it does for everyone else.

Buckets belong to a Solution

A Solution is the set of things that release together — an app, its database, its buckets. Attach a bucket and it ships with them:

waymaker host solution surface add my-product --kind storage_bucket --id <bucket-id>

A bucket belongs to exactly one Solution. waymaker storage create prints the command with the id already filled in.

What we refuse to store

Keys cannot climb out of their bucket. Anything containing .. — or an encoded form of it — is rejected rather than cleaned up. Cleaning up a suspicious key just means the next attempt uses a different encoding.

HTML is stored as a download. If you upload text/html or an SVG, it comes back as application/octet-stream. A public bucket serving markup on your own domain is an XSS hole with a storage bill, and the upload response tells you plainly when the type was changed.

Where the space comes from

Buckets count against the same allowance as Commander Files and Notes — one pool, one number, one place to top it up. A bucket write and a colleague's upload contend on the same counter, so neither can push you past your plan.

Deleting an object gives the space straight back. If you start an upload and never finish it, the space it reserved is returned within the hour — you do not have to do anything, and it never silently counts against you forever.

Every night the platform also recounts each bucket against what is actually stored, so the number you see is measured rather than remembered. Deleting a bucket while it still holds objects is refused, because that would leave you paying for files you could no longer see:

waymaker storage rmbucket old-uploads --delete-objects

From inside an Ambassador

Your function reads and writes buckets through ctx.storage — no keys, no setup:

export default async function handler(req, ctx) {
  const bucket = ctx.storage.bucket('customer-uploads')

  await bucket.put(`${tenantId}/report.pdf`, bytes, { contentType: 'application/pdf' })
  const obj = await bucket.get(`${tenantId}/report.pdf`)     // a Response, or null
  const page = await bucket.list({ prefix: `${tenantId}/` })
  const url = await bucket.signedUrl(`${tenantId}/report.pdf`, { expiresIn: 900 })
  await bucket.delete(`${tenantId}/old.pdf`)
}

Grant the ambassador first. A bucket it has not been granted does not exist as far as it is concerned:

waymaker storage grant add customer-uploads --ambassador <ambassador-id>
waymaker storage grant list customer-uploads

That grant belongs to the function, not to you. Two consequences worth knowing before you rely on either:

  • Removing a person from a bucket never breaks a running function.
  • Granting a function never gives whoever deployed it sight of the objects.

A function can only be a reader or a writer. There is no machine manager: nothing running unattended can make a bucket public, change who is on it, or delete it.

From an agent or assistant

The same operations are available as tools over MCP: host_storage_bucket_create, host_storage_bucket_list, host_storage_object_list, host_storage_object_upload_url, host_storage_object_url, host_storage_object_delete, and the membership pair.

Expiry

Give a bucket a TTL and anything older is removed automatically:

waymaker storage create scratch --ttl 30

Not yet

  • Public buckets on the CDN. Public is recorded and enforced for writes, but reads still go through a signed URL for now.